Na to zdanie można natknąć się, czytając o wzorcach projektowych.
Ale ja tego nie rozumiem, czy ktoś mógłby mi to wyjaśnić?
oop
design-patterns
interface
software-design
ooad
Never_had_a_name
źródło
źródło
Odpowiedzi:
Kodowanie względem interfejsu oznacza, że kod klienta zawsze zawiera obiekt interfejsu, który jest dostarczany przez fabrykę. Każda instancja zwrócona przez fabrykę byłaby typu Interface, którą musi zaimplementować każda klasa kandydująca do fabryki. W ten sposób program kliencki nie martwi się o implementację, a podpis interfejsu określa, jakie wszystkie operacje można wykonać. Można to wykorzystać do zmiany zachowania programu w czasie wykonywania. Pomaga również w pisaniu znacznie lepszych programów z punktu widzenia konserwacji.
Oto podstawowy przykład dla Ciebie.
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 }
EDYTOWAĆ
Zaktualizowałem powyższy przykład i dodałem abstrakcyjną
Speaker
klasę bazową. W tej aktualizacji dodałem funkcję do wszystkich głośników do „SayHello”. Wszyscy mówcy mówią „Hello World”. Jest to więc wspólna cecha o podobnej funkcji. Zapoznaj się z diagramem klas, a zobaczysz, że interfejsSpeaker
implementacji klasy abstrakcyjnejISpeaker
i oznaczaSpeak()
jako abstrakcyjny, co oznacza, że każda implementacja głośnika jest odpowiedzialna za implementacjęSpeak()
metody, ponieważ różni się odSpeaker
doSpeaker
. Ale wszyscy mówcy jednogłośnie mówią „Cześć”. Zatem w abstrakcyjnej klasie Speaker definiujemy metodę, która mówi „Hello World”, a każdaSpeaker
implementacja wyprowadza tęSayHello()
metodę.Rozważ przypadek, w którym
SpanishSpeaker
nie można się przywitać, więc w takim przypadku możesz zastąpićSayHello()
metodę dla języka hiszpańskiego i zgłosić odpowiedni wyjątek.Mogliśmy osiągnąć to zachowanie, po prostu dodając podstawowy głośnik klasy abstrakcyjnej i drobne modyfikacje w każdej implementacji, pozostawiając w ten sposób oryginalny program bez zmian. Jest to pożądana funkcja każdej aplikacji, która ułatwia jej konserwację.
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 }
źródło
List
jako typu, nadal możesz założyć, że dostęp losowy jest szybki przez wielokrotne wywoływanieget(i)
.Wyobraź sobie interfejs jako kontrakt między obiektem a jego klientami. Oznacza to, że interfejs określa rzeczy, które obiekt może robić, oraz sygnatury dostępu do tych rzeczy.
Implementacje to rzeczywiste zachowania. Załóżmy na przykład, że masz metodę sort (). Możesz zaimplementować QuickSort lub MergeSort. To nie powinno mieć znaczenia dla kodu klienta wywołującego sort, o ile interfejs się nie zmieni.
Biblioteki, takie jak Java API i .NET Framework, intensywnie wykorzystują interfejsy, ponieważ miliony programistów używają dostarczonych obiektów. Twórcy tych bibliotek muszą bardzo uważać, aby nie zmieniać interfejsu do klas w tych bibliotekach, ponieważ wpłynie to na wszystkich programistów korzystających z biblioteki. Z drugiej strony mogą zmieniać implementację tak bardzo, jak chcą.
Jeśli jako programista kodujesz w oparciu o implementację, to gdy tylko ona się zmieni, Twój kod przestanie działać. Pomyśl więc o zaletach interfejsu w ten sposób:
źródło
Oznacza to, że powinieneś spróbować napisać swój kod tak, aby używał abstrakcji (abstrakcyjnej klasy lub interfejsu) zamiast bezpośrednio implementacji.
Zwykle implementacja jest wstrzykiwana do kodu za pośrednictwem konstruktora lub wywołania metody. Zatem Twój kod wie o interfejsie lub klasie abstrakcyjnej i może wywołać wszystko, co jest zdefiniowane w tym kontrakcie. Jako rzeczywisty obiekt (implementacja interfejsu / klasy abstrakcyjnej) jest używany, wywołania działają na obiekcie.
Jest to podzbiór
Liskov Substitution Principle
(LSP), LSOLID
zasad.Przykładem w .NET może być kodowanie za pomocą
IList
zamiastList
lubDictionary
, więc możesz użyć dowolnej klasy, która implementujeIList
zamiennie w kodzie:Innym przykładem z biblioteki klas podstawowych (BCL) jest
ProviderBase
klasa abstrakcyjna - zapewnia to pewną infrastrukturę i, co ważne, oznacza, że wszystkie implementacje dostawców mogą być używane zamiennie, jeśli kodujesz przeciwko niej.źródło
Gdybyś miał napisać klasę samochodu w erze samochodów spalinowych, to jest duża szansa, że zaimplementowałbyś oilChange () jako część tej klasy. Ale kiedy zostaną wprowadzone samochody elektryczne, będziesz miał kłopoty, ponieważ nie ma potrzeby wymiany oleju w tych samochodach ani wdrożenia.
Rozwiązaniem problemu jest posiadanie interfejsu performMaintenance () w klasie Car i ukrycie szczegółów wewnątrz odpowiedniej implementacji. Każdy typ samochodu zapewniałby własną implementację funkcji performMaintenance (). Jako właściciel samochodu musisz tylko zająć się utrzymaniem () i nie martwić się o adaptację w przypadku ZMIANY.
Dodatkowe wyjaśnienie: jesteś właścicielem samochodu, który posiada wiele samochodów. Wytwarzasz usługę, którą chcesz zlecić na zewnątrz. W naszym przypadku chcemy zlecić obsługę techniczną wszystkich samochodów.
Nie chcesz martwić się o skojarzenie typu samochodu z usługodawcą. Wystarczy określić, kiedy chcesz zaplanować konserwację i ją wywołać. Właściwa firma serwisowa powinna wskoczyć i wykonać prace konserwacyjne.
Alternatywne podejście.
Wzywasz pracę i robisz to sam. Tutaj będziesz wykonywać odpowiednie prace konserwacyjne.
Jakie są wady drugiego podejścia? Możesz nie być ekspertem w znalezieniu najlepszego sposobu wykonania konserwacji. Twoim zadaniem jest prowadzić samochód i cieszyć się nim. Nie zajmować się utrzymaniem go.
Jakie są wady pierwszego podejścia? Istnieje narzut związany ze znalezieniem firmy itp. Jeśli nie jesteś wypożyczalnią samochodów, może to nie być warte wysiłku.
źródło
To stwierdzenie dotyczy łączenia. Jednym z potencjalnych powodów używania programowania obiektowego jest ponowne wykorzystanie. Na przykład możesz podzielić swój algorytm na dwa współpracujące ze sobą obiekty A i B. Może to być przydatne do późniejszego utworzenia innego algorytmu, który mógłby ponownie wykorzystać jeden z dwóch obiektów. Jednak gdy te obiekty komunikują się (wysyłają wiadomości - wywołują metody), tworzą między sobą zależności. Ale jeśli chcesz użyć jednego bez drugiego, musisz określić, co powinien zrobić inny obiekt C dla obiektu A, jeśli zastąpimy B. Te opisy nazywane są interfejsami. Umożliwia to obiektowi A komunikację bez zmian z innym obiektem zależnym od interfejsu. Oświadczenie, o którym wspomniałeś, mówi, że jeśli planujesz ponownie użyć jakiejś części algorytmu (lub bardziej ogólnie programu), powinieneś stworzyć interfejsy i polegać na nich,
źródło
Jak powiedzieli inni, oznacza to, że kod wywołujący powinien wiedzieć tylko o abstrakcyjnym rodzicu, a NIE o rzeczywistej klasie implementującej, która wykona pracę.
Pomaga to zrozumieć, DLACZEGO zawsze należy programować do interfejsu. Jest wiele powodów, ale dwa z najłatwiejszych do wyjaśnienia to
1) Testowanie.
Powiedzmy, że mam cały kod bazy danych w jednej klasie. Jeśli mój program wie o konkretnej klasie, mogę przetestować mój kod tylko wtedy, gdy naprawdę uruchamiam go na tej klasie. Używam -> do oznaczenia „rozmowy z”.
WorkerClass -> DALClass Jednak dodajmy interfejs do miksu.
WorkerClass -> IDAL -> DALClass.
Więc DALClass implementuje interfejs IDAL, a klasa robocza wywołuje TYLKO przez to.
Teraz, jeśli chcemy napisać testy dla kodu, moglibyśmy zamiast tego stworzyć prostą klasę, która zachowuje się jak baza danych.
WorkerClass -> IDAL -> IFakeDAL.
2) Ponowne użycie
Zgodnie z powyższym przykładem, powiedzmy, że chcemy przejść z SQL Server (z którego korzysta nasza konkretna klasa DAL) do MonogoDB. Wymagałoby to dużej pracy, ale NIE, gdybyśmy zaprogramowali interfejs. W takim przypadku po prostu piszemy nową klasę DB i zmieniamy (przez fabrykę)
WorkerClass -> IDAL -> DALClass
do
WorkerClass -> IDAL -> MongoDBClass
źródło
interfejsy opisują możliwości. pisząc kod imperatywny, mów raczej o możliwościach, których używasz, a nie o konkretnych typach lub klasach.
źródło