2015-11-19 12 views
5

ho una classe che contiene sia primitivi e personalizzati proprietà:Sostituire oggetto in eredità

public class Version_1 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
    public WeirdDad Weird { get; set; } 
    public MysteriousDad Mysterious { get; set; } 
} 

In futuro voglio estendere la classe con una proprietà int più una personalizzazione di uno dei miei oggetti personalizzati, in in questo modo:

public class Version_2 : Version_1 
{ 
    public string IdentityCode { get; set; } 
    public WeirdChild Weird { get; set; } 
} 

In classe VERSION_2 l'aspetto dell'oggetto WeirdDad è stato sostituito con il suo bambino WeirdChild quindi voglio sostituirlo. In questo esempio, invece, avrò nella classe Version_2 sia WeirdDad che WeirdChild.

Come implementeresti questo esempio?

+1

Perché non si astraggono proprietà papere strane e misteriose per restituire la base più astratta possibile con la versione 1? La modifica che si sta pianificando di eseguire con v 2 interromperà il codice client. Figlio e papà sono entrambi "Persone", quindi, restituiscilo Person, rendilo virtuale, e poi puoi scavalcare il tuo getter strano senza frenare i clienti perché weirdchild è anche una persona –

+1

Quello che stai cercando si chiama "nascondendo", tu probabilmente sta ricevendo un avvertimento sul compilatore quando compilate il codice sopra, ma vi nascondete esplicitamente dichiarando la proprietà "new", ovvero: public new WeirdChild Weird {get; impostato; } 'Tuttavia si noti che questo può essere fonte di confusione quando si opera con l'oggetto' Version_2' dichiarato come 'Version_1', come' Version_1 child = new Version_2(); ',' child.Wierd' opererà sulla classe base, non sul derivato uno. –

+0

Mi dispiace ma la tua domanda non è chiara. Non vuoi vedere la proprietà 'Weird' dalla classe base sull'istanza dell'oggetto' Version_2' e vuoi nasconderla? –

risposta

2

Pensate eredità + polimorfismo

Ecco po 'di codice, farò punti dopo .. .

public abstract class WeirdPerson() { 
    public virtual void DoThis() { 
     // base/default implementation as desired 
    } 

    public virtual void DoThat() { // ditto } 
    public abstract void GoWildAndCrazy; 
} 

public class WeirdChild : WeirdPerson { 
    public override void DoThis() { // weird child behavior} 
    // etc. 
} 

public class WeirdDad : WeirdPerson { 
    // override and add methods as needed. 
} 

public class Version_1 
{ 
    public string Name { get; protected set; } 
    public int Age { get; protected set; } 
    public WeirdPerson WeirdFamilyMember { get; protected set; } 
    public MysteriousDad Mysterious { get; protected set; } 

    public Version_1 (WeirdChild child, string name, int age, MysteriousDad Dad) { 

    } 
} 

public class Version_2 : Version_1 { 
    public Version_2 (WeirdDad dad, string name, int age, MysteriousDad grandfather) : base (dad, name, age, grandfather) {} 
} 

  • WeirdPerson - un concetto più generale che definisce le cose di base per tutti i sottotipi.
    • Sotto-tipo otterrà "fissa" le cose come Name-eredità
    • Sotto-tipo può override comportamento predefinito (metodi) - polimorfismo
    • ora non abbiamo roba divertente guardare: public class WeirdDad : WeirdChild
  • Costruttori
    • Version_1, Version_2 costruttori obbligano il client a fornire il sottotipo WeirdPerson corretto.
    • Il cliente deve darci tutti i materiali richiesti.
    • Possiamo convalidare/validare in modo incrociato gli argomenti in entrata.
  • Non abbiamo bisogno di nessun stinkin' interface
    • abstract classe ci permette di avere un comportamento di default (metodi) e stato di default (proprietà)
    • public metodi e le proprietà di qualsiasi classe è un interfaccia in senso generale. Non è necessario avere (parola chiave C#) interface per seguire il principio codice per interfacce non implementazione
    • abstract metodi di implementazione di sottoclasse. appena. piace. un.interface.
  • new
    • ATTENZIONE. Il metodo che nasconde separa la catena di ereditarietà in quel punto. Se ereditiamo da questa classe, non stiamo ereditando l'implementazione del metodo della classe base originale.
  • Liskov è felice.

Ulteriori Rifattorizzare

Fai la gerarchia Version parallelo la WeirdPerson gerarchia

Assumendo questo si inserisce nel vostro disegno. Penso che tra un anno sarai contento di averlo fatto.

public abstract class Version_X { 
    // all the original properties here. 

    // virtual and abstract methods as needed 

    // LOOK! standard-issue constructor! 
    protected Version_X (WeirdPerson person, ...) { // common validation, etc. } 
} 

public class Version_1 : Version_X { 
    public Version_1(WeirdChild child, ...) : base (child, ...) {} 
} 

public class Version_2 : Version_X { 
    public Version_2 (WeirdDad dad, ...) {} 
} 

Edit - Motivato da comment discussion with DVK

Il principio della conoscenza almeno dice un cliente non avrebbe dovuto conoscere i dettagli interni di utilizzare una classe. Avere bisogno di sapere come comporre il corretto Version e Weird è una violazione, si potrebbe obiettare.

Supponiamo che un costruttore predefinito, per Visitor diciamo, sia necessario altrove nel progetto generale. Ciò significa che un cliente può controllare la composizione Version/Weird.

Astrazione - accoppiamento lento - è nelle classi abstract. Le classi concrete devono necessariamente essere correttamente composte in modo che il "forte accoppiamento" sia inerente alla creazione di oggetti concreti (i parametri del costrutto di tipo esplicito), ma l'accoppiamento libero sottostante consente la flessibilità desiderata.

public enum WeirdFamily { Child, Dad, Mother, TheThing } 

public static class AdamsFamilyFactory() { 
    public static Version_X Create (WeirdFamily familyMember) { 
     switch (familyMember) { 
      case Dad: 
       return BuildAdamsDad(); 
     // . . . 
     } 
    } 
} 

public static class MunstersFactory() { // Munsters implementation } 

// client code 

List<Version_X> AdamsFamily = new List<Version_X>(); 
Version_X Pugsly = AdamsFamilyFactory.Create(WeirdFamily.Child); 
AdamsFamily.Add(Pugsly); 

List<Version_X> Munsters= new List<Version_X>(); 
Version_X Eddie= MunstersFactory.Create(WeirdFamily.Child); 
Munsters.Add(Eddie); 

DoTheMonsterMash(Munsters); 
DoTheMonsterMash(AdamsFamily); 

public void DoTheMonsterMash(List<Version_X> someWeirdFamily { 
    foreach (var member in someWeirdFamily) 
     member.GoWildAndCrazy(); 
} 
3

Credo che ciò che si vuole fare si chiama metodo nascosto. La classe VERSION_2 dovrebbe essere simile a questo:

public class Version_2 : Version_1 
{ 
    public string IdentityCode { get; set; } 
    public new WeirdChild Weird { get; set; } 
} 

Quando si vuole accedere alla proprietà 'Weird' dal VERSION_1, si dovrà effettuare una chiamata a base.Weird

Annotare, tuttavia, che questo non è raccomandato e è un odore di codice.

+1

Non dovrebbe esserci un "nuovo" lì da qualche parte? – CompuChip

+0

@CompuChip Oops. L'ho aggiunto xD – CodingMadeEasy

+1

Buona risposta. Questo è un po 'come usare un sedile di espulsione. Sì, è disponibile, ma dovrebbe essere usato solo se non hai assolutamente altre opzioni. – DVK

2

IMHO, VERSION_2 non dovrebbe ereditare VERSION_1 in quanto non può comportarsi come totalmente VERSION_1:

Version_2 version_2Instance = new Version_2(); 
version_2Instance.Weird = new WeirdDad(); 

mai dimenticare che l'ereditarietà si presuppone che si sta estendendo classe di base, non modifing esso.

Come sull'utilizzo classe template e una classe astratta di base:

public abstract class VersionBase<T> 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
    public abstract T Weird { get; set; } 
    public MysteriousDad Mysterious { get; set; } 
} 

Poi:

public class Version_1 : VersionBase<WeirdDad> 
{ 
    public override WeirdDad Weird { get; set; } 
} 

public class Version_2 : VersionBase<WeirdChild> 
{ 
    public string IdentityCode { get; set; } 
    public override WeirdChild Weird { get; set; } 
} 
+0

Questo dovrebbe essere buono se c'è un solo oggetto T (strano). Ma ho molti di questi oggetti personalizzati. –

1

È necessario ripensare ciò che si sta tentando di fare un po ', perché ciò che si sta proponendo si rompe il sostituto principale di Liskov.

Da: https://en.wikipedia.org/wiki/Liskov_substitution_principle

sostituibilità è un principio programmazione orientata agli oggetti. Si afferma che, in un programma per computer, se S è un sottotipo di T, allora gli oggetti di tipo T possono essere sostituiti con oggetti di tipo S (cioè oggetti di tipo S possono sostituire oggetti di tipo T) senza alterare nessuno degli auspicabili proprietà di quel programma (correttezza, compito svolto, ecc.).

Se desideri solo VERSION_2 per contenere un WeirdChild, e non qualsiasi tipo di classe che implementa WeirdDad, si può fare, ma è necessario eseguire il backup di un livello e cominciare a pensare in termini di farmaci generici, le interfacce, e/o classi astratte.

Ad esempio:

public interface IWeirdFamilyMember 
{ 
    // put some properties here 
} 

public interface IMyClass<T> where T: IWeirdFamilyMember 
{ 
    string Name { get; set; } 
    int Age { get; set; } 
    T Weird { get; set; } 
} 

Infine, è possibile definire le vostre classi in quanto tale:

public class Version_1 : IMyClass<WeirdDad> 
{ 
    string Name { get; set; } 
    int Age { get; set; } 
    WeirdDad Weird { get; set; } 
} 

public class Version_2 : IMyClass<WeirdChild> 
{ 
    string Name { get; set; } 
    int Age { get; set; } 
    WeirdChild Weird { get; set; } 
} 

Il problema con l'esempio di Ksv3n è che si stanno assumendo lo sviluppatore sta assegnando la sottoclasse WeirdChild di WeirdDad alla proprietà Weird, quando in realtà potrebbe trattarsi di qualsiasi tipo di WeirdDad o WeirdDad stesso. Questa è una ricetta per le eccezioni di runtime.

+0

* si sta ASSUMANDO che lo sviluppatore sta assegnando il WeirdChild * - possiamo supporre questo se esiste un costruttore 'Version_2' che richiede il tipo di sottoclasse desiderato.Questa situazione è una buona lezione sull'incapsulamento: non lasciare che i client impostino direttamente le proprietà (state, in generale). – radarbob

+0

@radarbob Sì, è possibile definire il costruttore predefinito come privato, rendere la proprietà Weird una proprietà di sola lettura e creare un costruttore che accetta il tipo di oggetto derivato corretto, ma successivamente introdurre altre complessità e potrebbe violare il codice esistente che si basa sull'avere un costruttore senza parametri. Nel complesso, si finisce per accoppiare strettamente un sacco di codice. – DVK

+0

** 1. ** Assegnare al client un costruttore predefinito significa che dobbiamo consentire al client di impostare le proprietà - e questo è il problema. ** 2. ** Se perde l'accoppiamento significa che è possibile comporre i tipi (sub) sbagliati, ovviamente è richiesto un accoppiamento più stretto. ** 3. ** Come è, l'accoppiamento più stretto è già lì - è implicito perché l'oggetto 'Weird' sbagliato infrangerà il codice. Quindi forzare il cliente a fare la cosa giusta * per il design * non rende l'aggancio più stretto rende il codice meno problematico ** 4. ** * e aderisce al principio di minima conoscenza. * – radarbob

0

Sono d'accordo con @CodingMadeEasy, che l'uso di nuovo è un codice odoroso.

Pertanto, suggerisco di utilizzare l'implementazione dell'interfaccia esplicita.

interface IVersion_1 
{ 
    WeirdDad Weird { get; set; } 
} 


interface IVersion_2 
{ 
    WeirdChild Weird { get; set; } 
} 

class Version_1 : IVersion_1 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
    public WeirdDad Weird { get; set; } 
    public MysteriousDad Mysterious { get; set; } 
} 

class Version_2 : Version_1, IVersion_2 
{ 
    public string IdentityCode { get; set; } 
    WeirdChild IVersion_2.Weird { get; set; } 
} 

Così nel client, a seconda del tipo di interfaccia, corretta proprietà sarà chiamato