Один натикається на цю фразу, читаючи про дизайнерські зразки.
Але я цього не розумію, може хтось мені це пояснить?
Один натикається на цю фразу, читаючи про дизайнерські зразки.
Але я цього не розумію, може хтось мені це пояснить?
Відповіді:
Інтерфейси - це лише контракти чи підписи, і вони нічого не знають про реалізацію.
Кодування проти інтерфейсу означає, що клієнтський код завжди містить об'єкт інтерфейсу, який постачається заводом. Будь-який екземпляр, що повертається на заводі, повинен мати тип інтерфейсу, який повинен мати будь-який заводський кандидат-клас. Таким чином, клієнтська програма не турбується про реалізацію, і підпис інтерфейсу визначає, які всі операції можна зробити. Це можна використовувати для зміни поведінки програми під час виконання. Це також допомагає вам писати набагато кращі програми з точки зору обслуговування.
Ось основний приклад для вас.
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
. Але всі спікери говорять "Привіт" одноголосно. Отже, в абстрактному класі Speaker ми визначаємо метод, який говорить "Hello World", і кожна Speaker
реалізація отримає SayHello()
метод.
Розглянемо випадок, коли SpanishSpeaker
не можна сказати привіт, тому в такому випадку ви можете перекрити SayHello()
метод для іспанського спікера та створити належне виключення.
Зауважте, що ми не внесли жодних змін у інтерфейс ISpeaker. І код клієнта і SpeakerFactory також залишаються незмінними. І цього ми досягаємо програмуванням до інтерфейсу .
І ми могли б досягти такої поведінки, просто додавши базовий абстрактний клас Speaker і деяку незначну модифікацію в кожній реалізації, таким чином залишаючи оригінальну програму незмінною. Це бажана особливість будь-якої програми, і це робить ваш додаток легко досяжним.
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)
.
Розгляньте інтерфейс як договір між об'єктом та його клієнтами. Тобто в інтерфейсі вказуються речі, які може зробити об'єкт, і підписи для доступу до цих речей.
Реалізація - це фактична поведінка. Скажімо, наприклад, у вас є метод 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();
}
Інший приклад з бібліотеки базових класів (BCL) - ProviderBase
абстрактний клас - це забезпечує певну інфраструктуру, і що важливо означає, що всі реалізації постачальника можуть використовуватися взаємозамінно, якщо ви кодуєте проти нього.
Якби ви писали автомобільний клас у епоху згоряння-автомобіля, є велика ймовірність, що ви застосували oilChange () у складі цього класу. Але, коли електромобілі будуть представлені, ви зіткнетесь з проблемою, оскільки для цих автомобілів немає жодної зміни масла, а також її впровадження.
Рішення проблеми - мати інтерфейс performMainnance () у класі автомобіля та заховати деталі у відповідній реалізації. Кожен тип автомобіля забезпечив би свою власну реалізацію для performans Maintenance (). Як власник Автомобіля, з чим вам доведеться мати справу - виконувати технічне обслуговування (), а не хвилюватися про пристосування, коли є ЗМІНА.
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. Це може бути корисно для подальшого створення іншого алгоритму, який може повторно використовувати той чи інший з двох об'єктів. Однак, коли ці об'єкти спілкуються (надсилають повідомлення - методи виклику), вони створюють залежності один від одного. Але якщо ви хочете використовувати один без іншого, вам потрібно вказати, що повинен робити якийсь інший об’єкт C для об’єкта A, якщо ми замінимо B. Ці описи називаються інтерфейсами. Це дозволяє об'єкту A спілкуватися без змін з різним об'єктом, що спирається на інтерфейс. Згадане вами твердження говорить про те, що якщо ви плануєте повторно використовувати деяку частину алгоритму (або загалом програму), вам слід створити інтерфейси та покластися на них,
Як говорили інші, це означає, що ваш код виклику повинен знати лише про абстрактний батьківський, а не про фактичний клас реалізації, який буде виконувати роботу.
Що допомагає зрозуміти це, ЧОМУ завжди слід запрограмувати інтерфейс. Причин багато, але дві найпростіші для пояснення - це
1) Тестування.
Скажімо, у мене є весь код бази даних в одному класі. Якщо моя програма знає про конкретний клас, я можу протестувати свій код, лише запустивши його проти цього класу. Я використовую -> означати "розмовляти".
WorkerClass -> DALClass Однак давайте додамо інтерфейс до суміші.
WorkerClass -> IDAL -> DALClass.
Таким чином, DALClass реалізує інтерфейс IDAL, і робочий клас ТІЛЬКИ дзвонить через це.
Тепер, якщо ми хочемо написати тести на код, замість цього ми могли б скласти простий клас, який просто подібно до бази даних.
WorkerClass -> IDAL -> IFakeDAL.
2) Повторне використання
Слідуючи наведеному вище прикладу, скажімо, що ми хочемо перейти від SQL Server (який використовує наш конкретний DALClass) до MonogoDB. Це займе значну роботу, але НЕ, якщо ми запрограмовані на інтерфейс. У цьому випадку ми просто записуємо новий клас БД і змінюємо (через завод)
WorkerClass -> IDAL -> DALClass
до
WorkerClass -> IDAL -> MongoDBClass