Mutabilità e getter

3

Nell'esempio seguente, ho una classe Person e una classe B che contiene un riferimento a una Persona.

La persona ha un metodo pubblico foo1 che può alterarne lo stato (è un oggetto mutevole).

Supponi che i clienti di classe B vogliano conoscere lo stipendio, il nome e l'indirizzo email dell'oggetto di persona. Devo riscrivere i getter dalla classe Person in classe B (senza fornire un metodo getPerson)? O forse getPerson è sufficiente, in quanto le persone saranno in grado di chiamare getSalary, getEmail e getName su di esso?

Sapendo che la persona è mutabile, dovremmo chiedere se vogliamo che i clienti di B modifichino quella persona chiamando foo1. Potremmo decidere di riscrivere i getters di Person in B o restituire un clone di tale oggetto Person, in modo che Person in B non venga modificato.

Che dire di un esempio più semplice, se Persona fosse immutabile (non ci sarebbe alcun metodo foo1 in questo caso)? In B, dovrei avere getPerson (che restituisce un clone, nel caso in cui Person diventasse mutabile in un secondo momento), o riscrivere tutti i getter di Person?

class Person{
    private String name;
    private int salary;
    private String email;
    //other private fields

    public void foo1(){
        //modify some fields here
    }

    public int getSalary(){
        return salary;
    }
    public String getName(){
        return name;
    }
    public String getEmail(){
        return email;
    }

}

class B{
    private Person person;

    public Person getPerson(){
        return person;
    }



}
    
posta user4205580 04.06.2016 - 13:02
fonte

6 risposte

8

Knowing that Person is mutable, we should ask if we want clients of B to modify that person by calling foo1. We may decide to rewrite Person' getters in B or return a clone of that Person object, so that Person in class B won't be modified.

Se non vuoi che i client modifichino l'oggetto restituito, un'altra opzione è quella di restituire un tipo di interfaccia che contiene solo i getter di Person.

What about a simpler example, if Person was immutable (there would be no foo1 method in this case)? In B, should I have getPerson (which returns a clone, just in case Person will become mutable at a later point), or rewrite all Person's getters?

No, dovresti avere getPerson () e restituire un riferimento all'istanza Person. Restituire un clone "nel caso in cui la persona diventasse mutabile in un secondo momento" è un caso ridicolo di YAGNI e l'aggiunta di molti getters individuali un caso orribile di duplicazione del codice.

Non pensare troppo. Soprattutto, cerca di rendere il tuo codice semplice .

    
risposta data 04.06.2016 - 13:30
fonte
6

Il tuo design di B è sbagliato quando perde A .

Ciò viola chiaramente la Legge di Demeter e genera Train Wreck Code .

La tua intenzione di comporre un oggetto è di estendere la sua conoscenza o comportamento : diciamo che vuoi costruire un'auto, la macchina ha bisogno di motore. quindi ha perfettamente senso dotare la macchina di un motore.

Come pilota, vuoi usare l' API (per così dire) della macchina, cioè non interferire con il motore ma guidare l'auto. Lo stesso vale per gli strumenti della tua classe B . Il fatto che B sia composto da A dovrebbe essere sconosciuto ai suoi strumenti.

Se un oggetto esterno ha bisogno di sapere qualcosa che A in B potrebbe rispondere, dovrebbe chiedere B .

Inoltre : se fornisci l'accesso a A , il risultato è un accoppiamento stretto tra i componenti di B e A , che si basano sull'implementazione di A e B , che non è, cosa vuoi.

    
risposta data 05.06.2016 - 10:16
fonte
3

Che cos'è la classe B che fa . Ricorda che OO parla di che dice agli oggetti di fare cose per te . Così (per esempio) se B è un PersonnelManager , potrebbe avere una collezione di oggetti Person e lavorare a un livello di astrazione più alto (ad esempio giveRaise(percentage) ). Non mi aspetto che possa proxy quegli oggetti Person per te (che è quello che stai implicando in alcuni dei precedenti)

L'eccezione a quanto sopra sarebbe se B fosse una sorta di DAO, ma poi non penso che le tue domande sopra si applichino affatto)

    
risposta data 04.06.2016 - 14:28
fonte
2

Per la classe B, il fatto che abbia un riferimento a una Persona è solo un dettaglio di implementazione, oppure fa parte delle specifiche della classe B che dovrebbe fornire l'accesso a un oggetto Persona?

Questa è la domanda, e la risposta decide quale interfaccia di classe B dovrebbe fornire. Potrebbe avere senso cambiare da un riferimento a Persona a un riferimento ad una classe diversa che potrebbe essere più adatta? Se hai dato accesso a una persona e improvvisamente non hai più una persona nella classe B, hai molte modifiche al codice da fare. D'altra parte, se quel riferimento a Persona è essenziale per gli utenti di classe B, rendilo disponibile.

    
risposta data 04.06.2016 - 20:49
fonte
2

Per espandere quello che stavo dicendo nei commenti, questo è un semplice esempio in Ruby di come puoi fare ciò che @ user4205580 sta chiedendo senza dover usare getter metodi. Per coloro che non hanno familiarità con i valori di Ruby che iniziano con @ sono variabili di istanza private.

Questo è un esempio rapido e sporco (scrivendolo in un coffeeshop in attesa del mio tè) e non pretendo che sia un design fantastico, ma piuttosto una prova rapida che nella maggior parte dei casi in cui pensi di aver bisogno di un getter pochi minuti a pensarci possono dimostrare che non lo fai.

class Job
  def initialize(name, start_time, end_time)
    @name = name
    @start_time = start_time
    @end_time = end_time
  end

  def starts_before(time)
    @start_time < time
  end

  def starts_after(time)
    @start_time > time
  end

  def conflict?(job)
    job.starts_after(@start_time) && job.starts_before(@end_time) # Test if job starts after we start, but before we end
  end
end


class Schedule
  def initialize
    @job_list = []
    @validators = []
  end

  def add_job(job)
    @job_list << job
  end

  def add_validator(validator)
    @validators << validator
  end

  def valid?
    @validators.all? do |validator|
      validator.run(@job_list)
    end
  end
end


class NoTimeConflictRule
  def run(jobs)
    # FYI product gets combo of every job eg [[1, 1], [1, 2], [1, 3], [2, 1]... etc
    # so we can compare every job to every other job. any? returns true if any
    jobs.product(jobs).each do |job_a, job_b|
      if (job_a != job_b) && (job_a.conflict?(job_b) || job_b.conflict?(job_a))
        return false # We have a time conflict, return false
      end
    end
    true # No conflict so return true for validation
  end
end


clean_shower = Job.new "clean shower", Time.utc(2016, 06, 12, 9, 45), Time.utc(2016, 06, 12, 10, 45)
walk_dog = Job.new "walk dog",  Time.utc(2016, 06, 12, 10), Time.utc(2016, 06, 12, 10, 30)
do_homework = Job.new "do homework", Time.utc(2016, 06, 12, 11), Time.utc(2016, 06, 12, 12)

invalid_schedule = Schedule.new
invalid_schedule.add_job clean_shower
invalid_schedule.add_job walk_dog
invalid_schedule.add_validator NoTimeConflictRule.new

puts "My invalid schedule is valid? - #{invalid_schedule.valid?}"

valid_schedule = Schedule.new
valid_schedule.add_job clean_shower
valid_schedule.add_job do_homework
valid_schedule.add_validator NoTimeConflictRule.new

puts "My valid schedule is valid? - #{valid_schedule.valid?}"

Questo codice funziona, e se lo fai otterrai questo output

$ruby stack_overflow.rb
My invalid schedule is valid? - false
My valid schedule is valid? - true

Aggiornamento: alcuni buoni libri sulla progettazione orientata agli oggetti che si concentrano sul comportamento

Pensiero degli oggetti

Progettazione di software orientato agli oggetti

Business Engineering with Object Technology

Ingegneria del software orientata agli oggetti

Smalltalk, Objects and Design

    
risposta data 12.06.2016 - 14:17
fonte
0

Mi concentrerò sulla parte foo1() della domanda.

Person has a public foo1 method that can alter its state (it's a mutable object).

...

What about a simpler example, if Person was immutable (there would be no foo1 method in this case)?

Suppongo che la tua incertezza su se Person debba essere immutabile riflette la tua incertezza sul fatto che foo1() debba esistere su Person . In altre parole, hai la possibilità di implementarlo in entrambi i modi.

Ecco un esempio di come implementare Person immutabile mentre si consente un foo1() che apparentemente ha accesso ad uno stato mutabile. La mia intuizione è che "lo stato mutabile" non ha bisogno di essere salvato sull'oggetto Person .

// mutable and contains the additional mutable state
// used by foo1() but is not stored as part of Person.
class Foo
{
    // ...
}

class FooCollection
{
    private HashMap<Person, Foo> mapPersonToFoo; 
}

class Person
{
    public Person(string name, ..., FooCollection fooCollection)
    {
        this.fooCollection = fooCollection;
    }
    public void foo1()
    {
        Foo myFoo = fooCollection.getFoo(this);
        // use mutable foo, as long as it does not modify Person
    }
}
    
risposta data 05.06.2016 - 07:10
fonte

Leggi altre domande sui tag