Si tratta di un uso improprio di "composizione sull'ereditarietà"?

3

Il sentimento generale sembra essere che la composizione è preferibile, nella maggior parte dei casi, sull'ereditarietà, poiché porta a un minore accoppiamento. Ma per un caso come questo,

module Publishable
  def new?
    @content.created_at > freshness_threshold
  end
end

class Book
  include Publishable

  def initialize(content)
    @content = content
  end

  def freshness_threshold
    t = Time.now
    Time.new(t.year)
  end
end

class Magazine
  include Publishable

  def initialize(content)
    @content = content
  end

  def freshness_threshold
    t = Time.now
    Time.new(t.year, t.month)
  end
end

dove i metodi implementati da un mixin dipendono dalla presenza di determinati metodi ( freshness_threshold ) e istanze variabili ( @content ) nella classe inclusa, sarebbe meglio usare l'ereditarietà? Questo è un esempio forzato, ma immagina che ci siano metodi aggiuntivi in Publishable che dipendono anche da metodi / variabili nella classe inclusa.

Sarebbe preferibile definire una classe genitore astratta con cui Book e Magazine discendono da?

    
posta ivan 19.02.2016 - 14:27
fonte

1 risposta

4

Mixin o tratti sono una terza alternativa all'ereditarietà o alla composizione. Che siano migliori dipende spesso dalla particolare implementazione. L'importante caratteristica dei mixin o dei tratti è che sono come classi astratte, ma possono essere applicati a più classi (e una classe può consumare più mixin). Ci sono - in alcune implementazioni - alcuni dettagli riguardanti l'ordine di risoluzione dei metodi che sono diversi tra classi astratte e mixin, ma altrimenti sono realmente equivalenti, fino al punto che questa differenza è irrilevante se il linguaggio consente l'ereditarietà multipla (MI) .

La cosa in cui i tratti sono davvero grandi è che forniscono zero o più metodi e richiedono zero o più metodi. Questo rende l'uso dei tratti abbastanza semplice. Tuttavia, la maggior parte delle lingue non dispone di spazi dei nomi per i metodi: tutte le definizioni vengono inserite negli stessi spazi dei nomi, il che può portare a conflitti tra i tratti.

Il tuo esempio non arriva nemmeno così lontano, perché il tuo mixin dichiara solo implicitamente i requisiti sull'utilizzo delle classi (che freshness_threshold deve essere una data e @content deve avere un campo created_at che deve essere una data ). Questo è anche peggio di una classe astratta, per esempio, Java - eccetto che Java non ha MI, quindi sei forzato per usare la composizione quando "eredita" più comportamenti.

Al contrario, la composizione ti obbliga ad essere più esplicito sul flusso di dati. Questo è buono, perché il tuo codice diventa più ovvio! Nel tuo particolare esempio, il mixin ha solo un singolo metodo, quindi per tutti gli scopi è equivalente a una funzione libera (o Proc o qualunque sia chiamata in Ruby). Questa funzione richiede due input: la data freshness_threshold e la data created_at . Data una tale funzione gratuita is_new(creation_date, threshold_date) , è ora molto semplice rendere questo comportamento disponibile alle tue classi senza dover inquinare l'interfaccia pubblica di questi oggetti con una data pubblica freshness_threshold . Per esempio. potrebbero definire

def new?
  t = Time.now
  return is_new(@content.created_at, Time.new(t.year, t.month))
end

Se i tuoi oggetti dipendono da is_new come dipendenza esplicita, ad esempio:

def initialize(content, is_new = is_new_default)
  @content = content
  @is_new = is_new
end

, quindi appare un importante vantaggio della composizione: possiamo fornire dinamicamente un'implementazione diversa (senza dover ricorrere a riflessioni o simili), che ci consente di testare facilmente e separatamente sia l'operazione is_new che il codice che dipende da questa operazione Questo vantaggio è meno ovvio per un semplice confronto come qui o altre operazioni "funzionalmente pure", ma sarà veramente importante se la dipendenza fornisce effetti collaterali esterni, ad es. richieste di rete, query di database, .... A meno che la lingua non abbia la gestione delle dipendenze integrata nel sistema di ereditarietà o nel sistema di mixaggio, è difficile da battere.

Se Book s e Magazine s erano entrambi utilizzati in un contesto polimorfico in cui dovresti scoprire se sono new? senza sapere se sono Book o Magazine , quindi definire un'interfaccia comune che dichiari che questo metodo sarebbe ragionevole (assumendo un linguaggio tipicamente più statico). Tuttavia, l'ereditarietà dell'interfaccia è completamente ortogonale rispetto all'ereditarietà rispetto alla discussione sulla composizione.

    
risposta data 19.02.2016 - 15:17
fonte

Leggi altre domande sui tag