Come capire il modello di progettazione per questa particolare soluzione?

1

Mentre lavoravo a un progetto, ho trovato una soluzione di design. Ho difficoltà a collegarlo a qualsiasi schema generale di progettazione o ad analizzare questa situazione nei dettagli. Questo mi sta anche impedendo di trovare un design migliore per questo.

Questa è una semplice analogia della mia soluzione - Sia Kangaroo che Tiger possono walk ma solo Kangaroo può jump e solo Tiger può run .

Animal<TLeg> where TLegs: ILegs
{
    TLegs Legs;
    void Walk(){ Legs.Walk(); }
}

Kangaroo: Animal<IJumpingLegs>
{
    void Jump() { Legs.Jump(); }
}

Tiger: Animal<IRunningLegs>
{
    void Run() { Legs.Run(); }
}

ILegs
{
   void Walk();
}

IRunningLegs : ILegs
{
   void Run();
}

IJumpingLegs : Ilegs
{
   void Jump();
}

[Modifica]
Descrizione del problema:
Qui, Kangaroo e Tiger condividono funzioni comuni e una proprietà Legs. Pertanto, ho una classe base per le funzioni comuni. La classe base deve avere accesso a questa proprietà Legs .
Anche le funzioni in sottoclassi fanno affidamento su Leg e in base al tipo di Leg, la sottoclasse dovrà chiamare diverse funzioni di questa proprietà.

In sostanza, due animali concreti necessitano di alcuni metodi comuni e i loro metodi non comuni (Jump and Run) richiedono un TYPE diverso di una proprietà comune.

Per questo problema, ho trovato la soluzione sopra descritta. Sperando di essere in grado di descrivere il mio problema, vorrei sapere se potrebbe esserci una soluzione alternativa.

Questo problema / soluzione suona familiare? C'è qualcosa che può essere migliorato?

    
posta db42 15.08.2013 - 08:55
fonte

4 risposte

4

Questo problema descritto mi ricorda Modelli di adattatori e facciate: essere adattivi .

Durante la lettura della tua domanda, ho anche ricordato un famoso vecchio detto divertente:

If it walks like a duck and quacks like a duck, then it must be a turkey wrapped with a duck adapter...

Potresti ottenere una spiegazione dettagliata dal seguente libro: Head First design Patterns

Inoltre, ci sono i SOLID principi e Principio di sostituzione di Liskov è uno di questi. Affronta anche questo problema con un sapore diverso.

If it looks like a duck, quacks like a duck, but needs batteries – you probably have the wrong abstraction

    
risposta data 15.08.2013 - 14:07
fonte
3

È un errore utilizzare l'ereditarietà con le classi e le interfacce Legs . (Ma se il tuo insegnante / capo insiste sul fatto che ... devi probabilmente arrenderti.)

Il tuo design funziona abbastanza bene per i requisiti che hai elencato. Ma supponiamo che esista un Horse che può sia run che jump . Come lo implementate?

Suggerisco che WalkingLegs , RunningLegs e JumpingLegs siano tutte classi separate senza ereditarietà. Il Tiger avrebbe WalkingLegs e RunningLegs , il Kangaroo avrebbe WalkingLegs e JumpingLegs , e il Horse avrebbe tutti e tre.

Il suggerimento sopra confonde la metafora Legs . Ciò che stiamo realmente modellando è il comportamento, non i tipi di gambe .

L'uso delle classi per rappresentare un comportamento fa parte del modello di strategia. Nel modello di strategia, i comportamenti hanno diverse implementazioni (in questo esempio, diversi strumenti di camminare, saltare, ecc.)

    
risposta data 15.08.2013 - 16:38
fonte
2

Per me, sembra un caso di brutta astrazione. Le classi base sono per il comportamento di astrazione uguale per tutti i bambini attesi. Quale è sbagliato nel tuo esempio. Nel tuo caso o vuoi Move metodo virtuale, e poi sostituirlo in animali concreti. O vuoi i metodi Jump e Run , ma poi non c'è niente di comune tra di loro, quindi astrarre via non ha senso.

E c'è una regola che mi piace quando faccio questo:

Abstractions should always be seen from point of who will use those abstractions. Not from the inside of the implementation.

Se stai scrivendo qualche astrazione e non hai scritto codice che userà questa astrazione, allora stai facendo qualcosa di sbagliato.

    
risposta data 15.08.2013 - 14:41
fonte
1

Probabilmente implementerei qualcosa in questo senso:

public abstract class Animal: ILegs
{
    public virtual void Walk(){/*Base logic for walking*/}
}

public interface ILegs
{
    void Walk();
}

public interface IJumpingLegs
{
    void Jump();
}

public interface IRunningLegs
{
    void Run();
}

public class Kangeroo : Animal, IJumpingLegs
{
    public void Jump()
    {
        //Logic for Jumping
    }
}

public class Tiger : Animal, IRunningLegs
{
    public void Run()
    {
        //Logic for Jumping
    }
}


//Custom Implementation
public class OtherAnimal : Animal
{
    public override void Walk() { /*Custom how I walk*/ }
}

Quando ho installato i miei oggetti, userei

OtherAnimal a = new OtherAnimal();
a.Walk(); //Custom override logic fro OtherAnimal class

Tiger t = new Tiger();
t.Run();  //Custom Run logic
t.Walk(); //Logic from Animal abstract class

Ogni classe Tiger / Kangeroo implementa solo l'interfaccia necessaria per ogni classe e non eredita alcuna logica inutile e non infrange il Principio di sostituzione di Liskov menzionato da @Yusubov.

La classe Tiger / Kangeroo usa anche la logica base Walk () della classe abstract Animal e ha l'opzione, se necessario, di sovrascriverla che si trova nella classe OtherAnimal.

Semplicemente non implementare una classe di pesci o simili estendendo l'animale. I pesci non camminano o hanno le gambe !!

    
risposta data 15.08.2013 - 15:47
fonte

Leggi altre domande sui tag