มีคนหนึ่งสะดุดกับวลีนี้เมื่ออ่านเกี่ยวกับรูปแบบการออกแบบ
แต่ฉันไม่เข้าใจมีใครช่วยอธิบายเรื่องนี้ให้ฉันได้ไหม
มีคนหนึ่งสะดุดกับวลีนี้เมื่ออ่านเกี่ยวกับรูปแบบการออกแบบ
แต่ฉันไม่เข้าใจมีใครช่วยอธิบายเรื่องนี้ให้ฉันได้ไหม
คำตอบ:
อินเทอร์เฟซเป็นเพียงสัญญาหรือลายเซ็นและพวกเขาไม่รู้อะไรเลยเกี่ยวกับการใช้งาน
การเข้ารหัสเทียบกับอินเทอร์เฟซรหัสไคลเอ็นต์จะถืออ็อบเจ็กต์อินเทอร์เฟซซึ่งจัดหาโดยโรงงานเสมอ อินสแตนซ์ใด ๆ ที่ส่งคืนโดยโรงงานจะเป็นอินเทอร์เฟซประเภทที่คลาสผู้สมัครจากโรงงานต้องนำไปใช้ ด้วยวิธีนี้โปรแกรมไคลเอ็นต์จะไม่กังวลเกี่ยวกับการนำไปใช้งานและลายเซ็นของอินเทอร์เฟซจะกำหนดสิ่งที่สามารถทำได้ สามารถใช้เพื่อเปลี่ยนลักษณะการทำงานของโปรแกรมในขณะทำงาน นอกจากนี้ยังช่วยให้คุณเขียนโปรแกรมได้ดีขึ้นจากมุมมองของการบำรุงรักษา
นี่คือตัวอย่างพื้นฐานสำหรับคุณ
public enum Language
{
    English, German, Spanish
}
public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}
[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}
public interface ISpeaker
{
    void Speak();
}
public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }
    #region ISpeaker Members
    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }
    #endregion
}
public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }
    #region ISpeaker Members
    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }
    #endregion
}
public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }
    #region ISpeaker Members
    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }
    #endregion
}
นี่เป็นเพียงตัวอย่างพื้นฐานและคำอธิบายที่แท้จริงของหลักการอยู่นอกเหนือขอบเขตของคำตอบนี้
ฉันได้อัปเดตตัวอย่างด้านบนและเพิ่มSpeakerคลาสฐานนามธรรม ในการอัปเดตนี้ฉันได้เพิ่มคุณสมบัติให้กับลำโพงทั้งหมดใน "SayHello" ผู้พูดทุกคนพูด "Hello World" นั่นเป็นคุณสมบัติทั่วไปที่มีฟังก์ชันคล้ายกัน อ้างถึงแผนภาพคลาสและคุณจะพบว่าSpeakerระดับนามธรรมใช้ISpeakerอินเตอร์เฟซและเครื่องหมายSpeak()เป็นนามธรรมซึ่งหมายความว่าการดำเนินงานแต่ละลำโพงเป็นผู้รับผิดชอบในการดำเนินการSpeak()วิธีเพราะมันแตกต่างจากการSpeaker Speakerแต่ผู้พูดทั้งหมดกล่าว "สวัสดี" เป็นเอกฉันท์ ดังนั้นในคลาส Abstract Speaker เราจึงกำหนดวิธีการที่ระบุว่า "Hello World" และSpeakerการนำไปใช้งานแต่ละครั้งจะได้มาซึ่งSayHello()วิธีการ
พิจารณากรณีที่SpanishSpeakerไม่สามารถกล่าวสวัสดีได้ในกรณีนี้คุณสามารถแทนที่SayHello()วิธีการสำหรับผู้พูดภาษาสเปนและเพิ่มข้อยกเว้นที่เหมาะสมได้
โปรดทราบว่าเราไม่ได้ทำการเปลี่ยนแปลงใด ๆ กับ Interface ISpeaker และรหัสไคลเอนต์และ SpeakerFactory ยังคงไม่ได้รับผลกระทบไม่เปลี่ยนแปลง และนี่คือสิ่งที่เราประสบความสำเร็จโดยการเขียนโปรแกรมเพื่อการเชื่อมต่อ
และเราสามารถบรรลุพฤติกรรมนี้ได้เพียงแค่เพิ่มลำโพงคลาสนามธรรมพื้นฐานและการปรับเปลี่ยนเล็กน้อยในการใช้งานแต่ละครั้งจึงทำให้โปรแกรมดั้งเดิมไม่เปลี่ยนแปลง นี่เป็นคุณสมบัติที่ต้องการของแอปพลิเคชันใด ๆ และทำให้แอปพลิเคชันของคุณสามารถบำรุงรักษาได้ง่าย
public enum Language
{
    English, German, Spanish
}
public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}
class Program
{
    [STAThread]
    static void Main()
    {
        //This is your client code.
        ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
        speaker.Speak();
        Console.ReadLine();
    }
}
public interface ISpeaker
{
    void Speak();
}
public abstract class Speaker : ISpeaker
{
    #region ISpeaker Members
    public abstract void Speak();
    public virtual void SayHello()
    {
        Console.WriteLine("Hello world.");
    }
    #endregion
}
public class EnglishSpeaker : Speaker
{
    public EnglishSpeaker() { }
    #region ISpeaker Members
    public override void Speak()
    {
        this.SayHello();
        Console.WriteLine("I speak English.");
    }
    #endregion
}
public class GermanSpeaker : Speaker
{
    public GermanSpeaker() { }
    #region ISpeaker Members
    public override void Speak()
    {
        Console.WriteLine("I speak German.");
        this.SayHello();
    }
    #endregion
}
public class SpanishSpeaker : Speaker
{
    public SpanishSpeaker() { }
    #region ISpeaker Members
    public override void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }
    public override void SayHello()
    {
        throw new ApplicationException("I cannot say Hello World.");
    }
    #endregion
}
List get(i)
                    คิดว่าอินเทอร์เฟซเป็นสัญญาระหว่างออบเจ็กต์กับลูกค้า นั่นคืออินเทอร์เฟซระบุสิ่งที่วัตถุสามารถทำได้และลายเซ็นสำหรับการเข้าถึงสิ่งเหล่านั้น
การนำไปปฏิบัติเป็นพฤติกรรมที่แท้จริง ตัวอย่างเช่นคุณมี method sort () คุณสามารถใช้ QuickSort หรือ MergeSort สิ่งนี้ไม่ควรมีความสำคัญกับการเรียกรหัสไคลเอ็นต์ตราบใดที่อินเทอร์เฟซไม่เปลี่ยนแปลง
ไลบรารีเช่น Java API และ. NET Framework ใช้อินเทอร์เฟซเป็นจำนวนมากเนื่องจากโปรแกรมเมอร์หลายล้านคนใช้อ็อบเจ็กต์ที่ให้มา ผู้สร้างไลบรารีเหล่านี้จะต้องระมัดระวังอย่างยิ่งที่จะไม่เปลี่ยนอินเทอร์เฟซไปยังคลาสในไลบรารีเหล่านี้เนื่องจากจะส่งผลกระทบต่อโปรแกรมเมอร์ทุกคนที่ใช้ไลบรารี ในทางกลับกันพวกเขาสามารถเปลี่ยนการใช้งานได้มากเท่าที่ต้องการ
หากในฐานะโปรแกรมเมอร์คุณเขียนโค้ดกับการนำไปใช้งานทันทีที่มีการเปลี่ยนแปลงโค้ดของคุณจะหยุดทำงาน ลองนึกถึงประโยชน์ของอินเทอร์เฟซด้วยวิธีนี้:
หมายความว่าคุณควรพยายามเขียนโค้ดของคุณเพื่อให้ใช้นามธรรม (คลาสนามธรรมหรืออินเทอร์เฟซ) แทนการนำไปใช้โดยตรง
โดยปกติการใช้งานจะถูกแทรกลงในโค้ดของคุณผ่านตัวสร้างหรือการเรียกใช้เมธอด ดังนั้นรหัสของคุณจึงรู้เกี่ยวกับอินเทอร์เฟซหรือคลาสนามธรรมและสามารถเรียกอะไรก็ได้ที่กำหนดไว้ในสัญญานี้ เนื่องจากมีการใช้อ็อบเจ็กต์จริง (การใช้อินเทอร์เฟซ / คลาสนามธรรม) การโทรจะดำเนินการกับอ็อบเจ็กต์
นี่คือชุดย่อยของLiskov Substitution Principle(LSP), L ของSOLIDหลักการ
ตัวอย่างใน. NET จะใช้รหัสIListแทนListหรือDictionaryดังนั้นคุณสามารถใช้คลาสใดก็ได้ที่ใช้IListแทนกันในโค้ดของคุณ:
// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
    // Do anything that IList supports
    return myList.Count();
}
อีกตัวอย่างหนึ่งจาก Base Class Library (BCL) คือProviderBaseคลาสนามธรรมซึ่งมีโครงสร้างพื้นฐานบางอย่างและที่สำคัญหมายความว่าการใช้งานของผู้ให้บริการทั้งหมดสามารถใช้แทนกันได้หากคุณเขียนโค้ดกับมัน
หากคุณต้องการเขียนคลาสรถยนต์ในยุคสันดาป - รถยนต์มีโอกาสมากที่คุณจะใช้ oilChange () เป็นส่วนหนึ่งของคลาสนี้ แต่เมื่อมีการเปิดตัวรถยนต์ไฟฟ้าคุณจะประสบปัญหาเนื่องจากไม่มีการเปลี่ยนถ่ายน้ำมันสำหรับรถยนต์เหล่านี้และไม่มีการนำไปใช้
วิธีแก้ปัญหาคือต้องมีอินเตอร์เฟส performMaintenance () ในคลาส Car และซ่อนรายละเอียดไว้ในการใช้งานที่เหมาะสม รถแต่ละประเภทจะจัดเตรียมการใช้งานของตัวเองสำหรับ performMaintenance () ในฐานะเจ้าของรถสิ่งที่คุณต้องจัดการคือการดำเนินการบำรุงรักษา () และไม่ต้องกังวลกับการปรับตัวเมื่อมีการเปลี่ยนแปลง
class MaintenanceSpecialist {
    public:
        virtual int performMaintenance() = 0;
};
class CombustionEnginedMaintenance : public MaintenanceSpecialist {
    int performMaintenance() { 
        printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
        return 0;
    }
};
class ElectricMaintenance : public MaintenanceSpecialist {
    int performMaintenance() {
        printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
        return 0;
    }
};
class Car {
    public:
        MaintenanceSpecialist *mSpecialist;
        virtual int maintenance() {
            printf("Just wash the car \n");
            return 0;
        };
};
class GasolineCar : public Car {
    public: 
        GasolineCar() {
        mSpecialist = new CombustionEnginedMaintenance();
        }
        int maintenance() {
        mSpecialist->performMaintenance();
        return 0;
        }
};
class ElectricCar : public Car {
    public: 
        ElectricCar() {
             mSpecialist = new ElectricMaintenance();
        }
        int maintenance(){
            mSpecialist->performMaintenance();
            return 0;
        }
};
int _tmain(int argc, _TCHAR* argv[]) {
    Car *myCar; 
    myCar = new GasolineCar();
    myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */
    myCar = new ElectricCar(); 
    myCar->maintenance(); 
    return 0;
}
คำอธิบายเพิ่มเติม: คุณเป็นเจ้าของรถที่มีรถยนต์หลายคัน คุณแกะออกบริการที่คุณต้องการจ้าง ในกรณีของเราเราต้องการจ้างงานซ่อมบำรุงรถยนต์ทั้งหมด
คุณไม่ต้องการกังวลเกี่ยวกับการเชื่อมโยงประเภทรถกับผู้ให้บริการ คุณเพียงระบุเวลาที่คุณต้องการกำหนดเวลาการบำรุงรักษาและเรียกใช้ บริษัท ผู้ให้บริการที่เหมาะสมควรเข้ามาดำเนินการบำรุงรักษา
แนวทางอื่น
คุณเรียกร้องงานและลงมือทำเอง ที่นี่คุณจะทำงานบำรุงรักษาที่เหมาะสม
ข้อเสียของแนวทางที่ 2 คืออะไร? คุณอาจไม่ใช่ผู้เชี่ยวชาญในการค้นหาวิธีที่ดีที่สุดในการบำรุงรักษา งานของคุณคือขับรถและสนุกกับมัน ไม่ต้องอยู่ในธุรกิจของการบำรุงรักษามัน
ข้อเสียของแนวทางแรกคืออะไร? มีค่าใช้จ่ายในการหา บริษัท ฯลฯ เว้นแต่คุณจะเป็น บริษัท รถเช่าอาจไม่คุ้มค่ากับความพยายาม
คำแถลงนี้เกี่ยวกับการมีเพศสัมพันธ์ เหตุผลหนึ่งที่เป็นไปได้สำหรับการใช้โปรแกรมเชิงวัตถุคือการนำกลับมาใช้ใหม่ ตัวอย่างเช่นคุณสามารถแยกอัลกอริทึมของคุณออกเป็นสองอ็อบเจ็กต์ที่ทำงานร่วมกัน A และ B สิ่งนี้อาจมีประโยชน์สำหรับการสร้างอัลกอริทึมอื่นในภายหลังซึ่งอาจนำมาใช้อย่างน้อยหนึ่งอ็อบเจ็กต์ทั้งสอง อย่างไรก็ตามเมื่อวัตถุเหล่านั้นสื่อสารกัน (ส่งข้อความ - วิธีการโทร) พวกมันจะสร้างการอ้างอิงซึ่งกันและกัน แต่ถ้าคุณต้องการใช้โดยไม่ใช้อีกชิ้นหนึ่งคุณต้องระบุสิ่งที่ควรทำสำหรับอ็อบเจ็กต์ A อื่น ๆ หากเราแทนที่ B คำอธิบายเหล่านั้นเรียกว่าอินเทอร์เฟซ สิ่งนี้ช่วยให้อ็อบเจ็กต์ A สื่อสารโดยไม่มีการเปลี่ยนแปลงกับอ็อบเจ็กต์อื่นที่อาศัยอินเทอร์เฟซ ข้อความที่คุณกล่าวถึงบอกว่าหากคุณวางแผนที่จะนำบางส่วนของอัลกอริทึมกลับมาใช้ใหม่ (หรือโดยทั่วไปคือโปรแกรม) คุณควรสร้างอินเทอร์เฟซและพึ่งพาพวกเขา
ดังที่คนอื่นกล่าวไว้นั่นหมายความว่ารหัสการโทรของคุณควรรู้เกี่ยวกับผู้ปกครองที่เป็นนามธรรมเท่านั้นไม่ใช่คลาสการใช้งานจริงที่จะทำงานได้
สิ่งที่ช่วยให้เข้าใจว่านี่คือเหตุผลที่คุณควรตั้งโปรแกรมให้กับอินเทอร์เฟซเสมอ มีหลายสาเหตุ แต่สองประการที่ง่ายที่สุดในการอธิบายคือ
1) การทดสอบ
สมมติว่าฉันมีรหัสฐานข้อมูลทั้งหมดในคลาสเดียว ถ้าโปรแกรมของฉันรู้เกี่ยวกับคลาสคอนกรีตฉันสามารถทดสอบโค้ดของฉันได้โดยการรันเทียบกับคลาสนั้นจริงๆ ฉันใช้ -> เพื่อหมายถึง "พูดคุยกับ"
WorkerClass -> DALClass อย่างไรก็ตามเรามาเพิ่มอินเทอร์เฟซในการผสมผสานกัน
WorkerClass -> IDAL -> DALClass
ดังนั้น DALClass จึงใช้อินเทอร์เฟซ IDAL และคลาสผู้ปฏิบัติงานจะเรียกใช้สิ่งนี้เท่านั้น
ตอนนี้ถ้าเราต้องการเขียนการทดสอบโค้ดเราสามารถสร้างคลาสง่ายๆที่ทำหน้าที่เหมือนฐานข้อมูลแทนได้
WorkerClass -> IDAL -> IFakeDAL
2) ใช้ซ้ำ
ตามตัวอย่างด้านบนสมมติว่าเราต้องการย้ายจาก SQL Server (ซึ่ง DALClass ที่เป็นรูปธรรมของเราใช้) ไปยัง MonogoDB สิ่งนี้จะต้องใช้เวลามาก แต่ไม่ใช่ถ้าเราตั้งโปรแกรมไว้ในอินเทอร์เฟซ ในกรณีนั้นเราแค่เขียนคลาส DB ใหม่และเปลี่ยน (ผ่านทางโรงงาน)
WorkerClass -> IDAL -> DALClass
ถึง
WorkerClass -> IDAL -> MongoDBClass
อินเทอร์เฟซอธิบายความสามารถ เมื่อเขียนโค้ดที่จำเป็นให้พูดถึงความสามารถที่คุณใช้มากกว่าประเภทหรือคลาสเฉพาะ