มีคนหนึ่งสะดุดกับวลีนี้เมื่ออ่านเกี่ยวกับรูปแบบการออกแบบ
แต่ฉันไม่เข้าใจมีใครช่วยอธิบายเรื่องนี้ให้ฉันได้ไหม
มีคนหนึ่งสะดุดกับวลีนี้เมื่ออ่านเกี่ยวกับรูปแบบการออกแบบ
แต่ฉันไม่เข้าใจมีใครช่วยอธิบายเรื่องนี้ให้ฉันได้ไหม
คำตอบ:
อินเทอร์เฟซเป็นเพียงสัญญาหรือลายเซ็นและพวกเขาไม่รู้อะไรเลยเกี่ยวกับการใช้งาน
การเข้ารหัสเทียบกับอินเทอร์เฟซรหัสไคลเอ็นต์จะถืออ็อบเจ็กต์อินเทอร์เฟซซึ่งจัดหาโดยโรงงานเสมอ อินสแตนซ์ใด ๆ ที่ส่งคืนโดยโรงงานจะเป็นอินเทอร์เฟซประเภทที่คลาสผู้สมัครจากโรงงานต้องนำไปใช้ ด้วยวิธีนี้โปรแกรมไคลเอ็นต์จะไม่กังวลเกี่ยวกับการนำไปใช้งานและลายเซ็นของอินเทอร์เฟซจะกำหนดสิ่งที่สามารถทำได้ สามารถใช้เพื่อเปลี่ยนลักษณะการทำงานของโปรแกรมในขณะทำงาน นอกจากนี้ยังช่วยให้คุณเขียนโปรแกรมได้ดีขึ้นจากมุมมองของการบำรุงรักษา
นี่คือตัวอย่างพื้นฐานสำหรับคุณ
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
อินเทอร์เฟซอธิบายความสามารถ เมื่อเขียนโค้ดที่จำเป็นให้พูดถึงความสามารถที่คุณใช้มากกว่าประเภทหรือคลาสเฉพาะ