Aperto Principio chiuso nei modelli di progettazione

7

Sono un po 'confuso su come il principio di Open Closed possa essere applicato nella vita reale. Il requisito in qualsiasi azienda cambia nel tempo. Secondo il principio di Open-Closed si dovrebbe estendere la classe invece di modificare la classe esistente. Per me, estendere una classe non sembra essere pratico per soddisfare il requisito. Lasciatemi fare un esempio con il sistema di prenotazione dei treni.

Nel sistema di prenotazione dei treni ci sarà un oggetto Ticket. Ci possono essere diversi tipi di biglietti Biglietto normale, Biglietto di concessione, ecc. Il biglietto è di classe astratta e RegularTicket e ConcessionTickets sono lezioni concrete. Poiché tutti i biglietti avranno il metodo PrintTicket che è comune, quindi è scritto in Ticket che è una classe astratta di base. Diciamo che ha funzionato bene per alcuni mesi. Ora arriva un nuovo requisito, che afferma di cambiare il formato del biglietto. Possono essere aggiunti altri campi sul biglietto stampato o il formato può essere modificato. Per soddisfare questo requisito ho le seguenti opzioni

  1. Modifica il metodo PrintTicket () nella classe astratta Ticket. Ma questo viola il principio Open-Closed.
  2. Sostituisci il metodo PrintTicket () nelle classi figlie, ma questo duplicherà la logica di stampa che è una violazione del principio DRY (Non ripeterti).

Quindi le domande sono

  1. Come posso soddisfare i requisiti aziendali di cui sopra senza violare il principio Open / Closed.
  2. Quando la classe dovrebbe essere chiusa per la modifica? Quali sono i criteri per considerare la classe chiusa per la modifica? È dopo l'implementazione iniziale della classe o potrebbe essere dopo la prima implementazione in produzione o potrebbe essere qualcos'altro.
posta parag 14.04.2016 - 16:13
fonte

5 risposte

5

I have following options

  • Modify the PrintTicket() method in Ticket abstract class. But this will violate Open-Closed principle.
  • Override the PrintTicket() method in child classes but this will duplicate the printing logic which is violation of DRY(Do not repeat yourself) principle.

Dipende dal modo in cui viene implementato PrintTicket . Se il metodo deve considerare le informazioni provenienti da sottoclassi, deve fornire un modo per fornire ulteriori informazioni.

Inoltre, puoi eseguire l'override senza ripetere il codice. Ad esempio, se chiami il metodo della classe base dall'implementazione, eviti la ripetizione:

class ConcessionTicket : Ticket {
    public override string PrintTicket() {
        return $"{base.PrintTicket()} (concession)"
    }
}

How can I satisfy above business requirement without violating the Open/Closed principle?

Il modello Modello fornisce una terza opzione: implementa PrintTicket nella classe base e si basano su classi derivate per fornire ulteriori dettagli, se necessario.

Ecco un esempio che utilizza la tua gerarchia di classi:

abstract class Ticket {
    public string Name {get;}
    public string Train {get;}
    protected virtual string AdditionalDetails() {
        return "";
    }
    public string PrintTicket() {
        return $"{Name} : {Train}{AdditionalDetails()}";
    }
}

class RegularTicket : Ticket {
    ... // Uses the default implementation of AdditionalDetails()
}


class ConcessionTicket : Ticket {
    protected override string AdditionalDetails() {
        return " (concession)";
    }
}

What are the criteria to consider class is closed for modification?

Non è la classe che dovrebbe essere chiusa alla modifica, ma piuttosto l'interfaccia di quella classe (intendo "interfaccia" in senso lato, cioè una raccolta di metodi e proprietà e il loro comportamento, non la costruzione del linguaggio). La classe nasconde la sua implementazione, quindi i proprietari della classe possono modificarla in qualsiasi momento, purché il suo comportamento esternamente visibile rimanga invariato.

Is it after initial implementation of the class or may be after first deployment in production or may be something else?

L'interfaccia della classe deve rimanere chiusa dopo la prima volta che la pubblichi per uso esterno. Le classi di uso interno restano aperte al refactoring per sempre, perché puoi trovare e correggere tutti i suoi usi.

A parte i casi più semplici, non è pratico aspettarsi che la propria gerarchia di classi copra tutti gli scenari di utilizzo possibili dopo un certo numero di iterazioni di refactoring. I requisiti aggiuntivi che richiedono metodi completamente nuovi nella classe base vengono regolarmente, quindi la tua classe rimane aperta alle modifiche da te per sempre.

    
risposta data 14.04.2016 - 16:25
fonte
2

Iniziamo con una linea semplice di principio Aperto-chiuso - "Open for extension Closed for modification" . Considera il tuo problema Vorrei progettare le classi in modo che il Biglietto base fosse responsabile della stampa di informazioni comuni sui biglietti e altri tipi di Ticket sarebbero responsabili della stampa del proprio formato sulla stampa di base.

abstract class Ticket
{
    public string Print()
    {
        // Print base properties here. and return 
        return PrintBasicInfo() + AddionalPrint();
    }

    protected abstract string AddionalPrint();

    private string PrintBasicInfo()
    {
        // print base ticket info
    }
}

class ConcessionTicket : Ticket
{
    protected override string AddionalPrint()
    {
        // Specific to Conecession ticket printing
    }
}

class RegularTicket : Ticket
{
    protected override string AddionalPrint()
    {
        // Specific to Regular ticket printing
    }
}

In questo modo applicando qualsiasi nuovo tipo di ticket che verrà introdotto nel sistema, verrà implementata la propria funzione di stampa oltre alle informazioni di base sulla stampa dei biglietti. Base Ticket è ora chiuso per la modifica ma aperto per l'estensione fornendo un metodo aggiuntivo per i suoi tipi derivati.

    
risposta data 14.04.2016 - 16:41
fonte
2

Il problema non è che stai violando il principio Open / Closed, il problema è che stai violando il Principio di Responsabilità Unica.

Questo è letteralmente un esempio di libro scolastico di un problema SRP o come wikipedia afferma:

As an example, consider a module that compiles and prints a report. Imagine such a module can be changed for two reasons. First, the content of the report could change. Second, the format of the report could change. These two things change for very different causes; one substantive, and one cosmetic. The single responsibility principle says that these two aspects of the problem are really two separate responsibilities, and should therefore be in separate classes or modules. It would be a bad design to couple two things that change for different reasons at different times.

Le diverse classi di Ticket possono cambiare perché aggiungi nuove informazioni a loro. La stampa del biglietto può cambiare perché hai bisogno di un nuovo layout, quindi dovresti avere una TicketPrinter che accetta i ticket come input.

Le tue classi Ticket possono quindi implementare diverse interfacce per fornire dati di tipi diversi o fornire un tipo di modello di dati.

    
risposta data 23.05.2016 - 21:58
fonte
1

Modify the PrintTicket() method in Ticket abstract class. But this will violate Open-Closed principle.

Questo non viola il principio di open-closed. Il codice cambia continuamente, ecco perché SOLID è importante, quindi il codice rimane gestibile, flessibile, modificabile. Non c'è niente di sbagliato nel cambiare codice.

L'OCP è più incentrato sulle classi esterne che non sono in grado di manomettere la funzionalità prevista di una classe.

Ad esempio, ho una classe A che accetta una stringa nel costruttore e verifica che questa stringa abbia un determinato formato chiamando un metodo per verificare la stringa. Anche questo metodo di verifica deve essere utilizzabile dai client di A , quindi nell'implementazione ingenua è stato creato public .

Ora posso 'rompere' questa classe ereditandoci da e sovrascrivendo il metodo di verifica per restituire sempre true . A causa del polimorfismo, posso comunque utilizzare la mia sottoclasse ovunque sia possibile utilizzare A , creando eventualmente comportamenti indesiderati nel mio programma.

Sembra una cosa silenziosa da fare, ma in una base di codice più complessa, potrebbe essere un "errore onesto". Per conformarsi a OCP, la classe A deve essere progettata in modo tale da rendere impossibile l'errore.

Potrei farlo, ad esempio, rendendo il metodo di verifica final .

    
risposta data 14.04.2016 - 16:42
fonte
0

L'ereditarietà non è l'unico meccanismo che può essere utilizzato per soddisfare i requisiti di OCP e, nella maggior parte dei casi, direi che raramente è il migliore: di solito ci sono modi migliori, purché pianifichi un po 'più avanti prendere in considerazione il tipo di modifiche di cui hai probabilmente bisogno.

In questo caso, direi che se ti aspetti di ottenere frequenti modifiche di formato al tuo sistema di stampa dei biglietti (e sembra una scommessa abbastanza sicura per me), usare un sistema di template sarebbe molto sensato. Ora, non è necessario toccare il codice: tutto ciò che dobbiamo fare per soddisfare questa richiesta è modificare un modello (purché il tuo sistema di template possa accedere a tutti i dati richiesti, comunque).

In realtà, un sistema non può mai essere interamente chiuso contro modifiche, e persino chiuderlo richiederebbe una quantità enorme di sforzi inutili, quindi devi fare delle chiamate di valutazione su quale tipo di cambiamenti sono probabili e strutturare il tuo codice di conseguenza .

    
risposta data 20.05.2016 - 01:46
fonte

Leggi altre domande sui tag