Composition or Inheritance – How to Choose?

Posted by | No Tags | Egy csésze kávé · Szoftverfejlesztés | Nincs hozzászólás a(z) Composition or Inheritance – How to Choose? bejegyzéshez

Egy korábbi előadásban a kompozíció előnyeit vizsgáltuk a származtatással szemben. Ennek folytatásaként most azzal foglalkoznék, hogy hogyan tudjuk eldönteni, hogy vajon mikor érdemes származtatni és mikor kompozíciót használni?

Könnyen beleeshetünk abba a hibába, hogy egy feladatot származtatással kezdünk megoldani, ami önmagában még nem rossz dolog, de ha az igények változása mellett is ragaszkodunk ehhez a szerkezethez, annak már lehetnek negatív következményei.

Nézzünk meg egy konkrét példát:
Induljunk ki abból, hogy van egy ős osztályunk és annak néhány leszármazottja. Ahogy halad előre a projekt, észrevesszük, hogy néhány meglevő leszármazott hasonló részeket tartalmaz. Mivel szeretnénk követni a DRY elvet, ezért a közös részeket kiemeljük egy közös ős osztályba. Ezzel háromszintűvé válik a származtatási hierarchiánk. Majd idővel az igények változása, illetve új igények érkezésével észrevesszük, hogy néhány új leszármazottnak olyan funkcióra volna szüksége, amely néhány más implementációban már megvan, persze esetleg olyan más funkciókkal együtt, amelyekre itt nincs szükségünk. Ekkor itt is át kell szervezni a kódot, s itt is megpróbálhatunk csoportosítani, újabb ősosztályok létrehozásával, újabb szinteket bevéve a származtatási hierarchiánkba.
Vajon hova fog ez vezetni? Vajon mikor vesszük észre, hogy már csak megbonyolítjuk saját életünket? Vajon a projekt határideje mennyire szól bele abba, hogy vegyük az erőt és bátorságot, és a már nem hatásos konstrukciót javítsuk?

Don’t be a slave to history. Don’t let existing code dictate future code. All code can be replaced if it is no longer appropriate. Even within one program, don’t let what you’ve already done constrain what you do next — be ready to refactor… This decision may impact the project schedule. The assumption is that the impact will be less than the cost of /not/ making the change.

― Andrew Hunt, The Pragmatic Programmer: From Journeyman to Master

 

 

 

Érdemes Tim Boudreau hozzászolását elolvasni e témakörben:

Common problems to the use of inheritance as I see it are:

  • Innocent acts can have unexpected results – The classic example of this is calls to overridable methods from the superclass constructor, before the subclasses instance fields have been initialized. In a perfect world, nobody would ever do that. This is not a perfect world.
  • It offers perverse temptations for subclassers to make assumptions about order of method calls and such – such assumptions tend not to be stable if the superclass may evolve over time. See also my toaster and coffee pot analogy.
  • Classes get heavier – you don’t necessarily know what work your superclass is doing in its constructor, or how much memory it’s going to use. So constructing some innocent would-be lightweight object can be far more expensive than you think, and this may change over time if the superclass evolves
  • It encourages an explosion of subclasses. Classloading costs time, more classes costs memory. This may be a non-issue until you’re dealing with an app on the scale of NetBeans, but there, we had real issues with, for example, menus being slow because the first display of a menu triggered massive class loading. We fixed this by moving to more declarative syntax and other techniques, but that cost time to fix as well.
  • It makes it harder to change things later – if you’ve made a class public, swapping the superclass is going to break subclasses – it’s a choice which, once you’ve made the code public, you’re married to. So if you’re not altering the real functionality to your superclass, you get much more freedom to change things later if you use, rather than extend the thing you need. Take, for example, subclassing JPanel – this is usually wrong; and if the subclass is public somewhere, you never get a chance to revisit that decision. If it’s accessed as JComponent getThePanel() , you can still do it (hint: expose models for the components within as your API).
  • Object hierarchies don’t scale (or making them scale later is much harder than planning ahead) – this is the classic “too many layers” problem. I’ll go into this below, and how the AskTheOracle pattern can solve it (though it may offend OOP purists).

My take on what to do, if you do allow for inheritance, which you may take with a grain of salt is:

  • Expose no fields, ever, except constants
  • Methods shall be either abstract or final
  • Call no methods from the superclass constructor

all of this applies less to small projects than large ones, and less to private classes than public ones.


No Comments

Leave a comment