2012-02-27 13 views
6

Chciałbym odnieść się do przykładu, który był używany wcześniej na SO z Duck and Electric Duck:C# jest interfejs zarzucali naruszenie zasada podstawienia liskov

public interface IDuck 
{ 
    void Swim(); 
} 

public class Duck : IDuck 
{ 
    public void Swim() 
    { 
     //do something to swim 
    } 
} 

public class ElectricDuck : IDuck 
{ 
    public void Swim() 
    { 
     if (!IsTurnedOn) 
      return; 

     //swim logic 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 
} 

Oryginalny naruszenie przez LSP będzie wyglądać tak:

void MakeDuckSwim(IDuck duck) 
    { 
     if (duck is ElectricDuck) 
      ((ElectricDuck)duck).TurnOn(); 
     duck.Swim(); 
    } 

Jednym z rozwiązań przez autora było umieścić wewnątrz metody Logic pływać kaczka elektrycznego, aby włączyć się na:

public class ElectricDuck : IDuck 
{ 
    public void Swim() 
    { 
     if (!IsTurnedOn) 
      TurnOn(); 

     //swim logic 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 
} 

mam natknąć innych scenariuszy, gdzie rozszerzony interfejs może być utworzone, który obsługuje jakiś inicjalizacji:

public interface IInitializeRequired 
{ 
    public void Init(); 
} 

elektryczny Duck może następnie zostać przedłużony z tego interfejsu:

public class ElectricDuck : IDuck, IInitializeRequired 
{ 
    public void Swim() 
    { 
     if (!IsTurnedOn) 
      return; 

     //swim logic 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 

    #region IInitializeRequired Members 

    public void Init() 
    { 
     TurnOn(); 
    } 

    #endregion 
} 

EDIT : Powód rozszerzonego interfejsu Opiera się na twierdzeniu autora, że ​​automatyczne włączanie metody pływania może mieć inne niepożądane wyniki.

Następnie metoda zamiast sprawdzania i rzucając się do określonego rodzaju może patrzeć przez dłuższy interfejsu Zamiast:

void MakeDuckSwim2(IDuck duck) 
    { 
     var init = duck as IInitializeRequired; 
     if (init != null) 
     { 
      init.Init(); 
     } 

     duck.Swim(); 
    } 

Fakt, że zrobiłem koncepcję inicjowania bardziej abstrakcyjne następnie stworzyć rozszerzony interfejs o nazwie IElectricDuck z Metoda TurnOn() może sprawiać wrażenie, że postąpiłem słusznie, jednak cała koncepcja Init może istnieć tylko z powodu elektrycznej kaczki.

Czy jest to lepszy sposób/rozwiązanie, czy jest to tylko naruszenie LSP w przebraniu.

Dzięki

+0

Dlaczego nie tylko zmniejszyć problem z kolejnym uogólnieniem? Krótko mówiąc na przykład: "IDuck.HasEnergy" itp., Możesz wymusić "uzyskanie energii", która będzie taka sama w zasadzie dla każdej kaczki, ale różni się w szczegółach (można jeść, inne wkładane baterie lub włączyć). –

+0

Zastanawiałem się nad tymi kwestiami, jednak pozostaje pytanie, czy metoda nie bierze argumentów za tymi interfejsami, ale raczej na najwyższym poziomie, a następnie odlewania interfejsu, może to spowodować takie same problemy z naruszeniem LSP, gdy dodawane są inne uogólnienia, na przykład CanGetEnergy() teraz nagle wszystkie metody będą musiały dodać tę logikę. – Andre

+0

Na czym polega problem polegający na tym, aby logika wewnątrz metody pływania elektrycznego kaczki mogła się włączyć? –

Odpowiedz

4

chciałbym jeszcze rozważyć Ostatnim przykładem jako naruszenie LSP ponieważ logicznie zrobić dokładnie to. Jak już powiedziałeś, nie ma żadnej koncepcji inicjalizacji, jest ona po prostu zhakowana.

Rzeczywiście, twoja metoda MakeDuckSwim nie powinna wiedzieć o jakiejkolwiek specyfikacji kaczki (czy powinna być najpierw zainicjalizowana, karmiona jakimś miejscem docelowym po inicjalizacji, itp.). To wystarczy, aby dostarczona kaczka pływała!

Trudno powiedzieć na tym przykładzie (ponieważ nie jest prawdziwy), ale wygląda jak "górny" jest fabryka lub coś, co tworzy konkretną kaczkę.

Możliwe, że tęsknisz za koncepcją fabryki tutaj?

Jeśli był jeden, potem To powinien wiedzieć, co kaczka jest stworzenie dokładnie tak prawdopodobnie to powinny być odpowiedzialne umieć zainicjować kaczkę, a reszta kodu po prostu działa z IDuck bez żadnych " ifs "wewnątrz behawioralnych metod.

Oczywiście można wprowadzić pojęcie "inicjalizacji" bezpośrednio do interfejsu IDuck.Powiedzmy, że "normalna" kaczka musi być karmiona, elektryczna musi być włączona, itd :) Ale brzmi trochę podejrzanie :)

+0

Witaj Alexey, dziękuję za odpowiedź, co sądzisz o czasach, w których twoja metoda korzysta z interfejsu, ale musi również wykonać coś dodatkowego, jeśli istnieje rozszerzony interfejs, na przykład IDisposable, jeśli potrzebujesz aby pozbyć się przedmiotu, nie ma innego sposobu na rzucenie, a jeśli sprawdzisz do jednorazowego użytku i zutylizujesz? To najprostszy przykład, jaki mogłem wymyślić: – Andre

+0

@Andre Disposable to inna historia. Jednorazowe to _technical_, a nie _functional_. Innymi słowy, jednorazowe nie ma nic wspólnego z logiką biznesową. To samo dotyczy IComparable, IEquatable itp. Nie reprezentują one zachowań z punktu widzenia logiki biznesowej. Nawet tam, kiedy zajmujesz się sprzętem jednorazowym, potrzebujesz tylko interfejsu IDisposable, nie obchodzi cię, co to jest de facto. –

0

Myślę, że najpierw trzeba odpowiedzieć na to pytanie o elektryczne kaczki - do włączają się automatycznie, gdy ktoś prosi ich o pływanie? Jeśli tak, włącz je w metodzie Swim.

Jeśli nie, to klient jest odpowiedzialny za włączenie kaczki, a równie dobrze można po prostu rzucić InvalidOperationException, jeśli kaczka nie może pływać, ponieważ jest wyłączona.

5

Jest to naruszenie LSP w przebraniu. Twoja metoda akceptuje numer IDuck, ale wymaga weryfikacji typu dynamicznego (niezależnie od tego, czy IDuck implementuje IInitializeRequired).


Jedna możliwość to naprawić byłoby pogodzić się z faktem, że niektóre kaczki wymaga inicjalizacji i przedefiniować interfejs:

public interface IDuck 
{ 
    void Init(); 

    /// <summary> 
    /// Swims, if the duck has been initialized or does not require initialization. 
    /// </summary> 
    void Swim(); 
} 

Innym rozwiązaniem jest przyjąć, że niezainicjowany ElectricDuck nie jest naprawdę kaczka; w ten sposób, że nie realizuje IDuck:

public class ElectricDuck 
{ 
    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 

    public IDuck GetIDuck() 
    { 
     if (!IsTurnedOn) 
      throw new InvalidOperationException(); 

     return new InitializedElectricDuck(); // pass arguments to constructor if required 
    } 

    private class InitializedElectricDuck : IDuck 
    { 
     public void Swim() 
     { 
      // swim logic 
     } 
    } 
} 
+0

Cześć Heinzi, to wygląda na lepsze podejście, ale jak byś to odniósł do innych form odlewania interfejsu, na przykład w .NET, gdzie posiadasz odniesienia do niektórych interfejsów i na Disposal you to var disp = (Someinterface as IDisposable)? – Andre

+0

@Andre: Zazwyczaj kod tworzący obiekt jest odpowiedzialny za jego usunięcie (za pomocą instrukcji 'using'), więc sądzę, że jest to sytuacja dość rzadka. – Heinzi

0
public interface ISwimBehavior 
{ 
    void Swim(); 
} 

public interface IDuck 
{ 
    void ISwimBehavior { get; set; } 
} 

public class Duck : IDuck 
{ 
    ISwimBehavior SwimBehavior { get { return new SwimBehavior(); }; set; } 
} 

public class ElectricDuck : IDuck 
{ 
    ISwimBehavior SwimBehavior { get { return new EletricSwimBehavior(); }; set; } 
} 

Klasy Zachowanie:

public class SwimBehavior: ISwimBehavior 
{ 
    public void Swim() 
    { 
     //do something to swim 
    } 
} 

public class EletricSwimBehavior: ISwimBehavior 
{ 
    public void Swim() 
    { 
     if (!IsTurnedOn) 
      this.TurnOn(); 

     //do something to swim 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 
} 
+2

Nie widzę, jak to pomaga? Czy możesz dodać komentarze? –

0

Może coś takiego:

public interface IDuck 
{ 
    bool CanSwim { get; } 
    void Swim(); 
} 

public class Duck : IDuck 
{ 
    public void Swim() 
    { 
     //do something to swim 
    } 

    public bool CanSwim { get { return true; } } 
} 

public class ElectricDuck : IDuck 
{ 
    public void Swim() 
    { 
     //swim logic 
    } 

    public void TurnOn() 
    { 
     this.IsTurnedOn = true; 
    } 

    public bool IsTurnedOn { get; set; } 
    public bool CanSwim { get { return IsTurnedOn; } } 
} 

Klient zostanie zmieniony tak:

void MakeDuckSwim(IDuck duck) 
{ 
     if (duck.CanSwim) 
     { 
      duck.Swim(); 
     } 
} 
Powiązane problemy