Az objektum-orientált tervezési alapelvek kritikai vizsgálata Kusper Gábor, EKF, MatInf Intézet Márien Szabolcs, Wit-Sys ZRt.
Tartalom Tervezési alapelvek: GOF1, GOF2, SRP, OCP Egy új elv: DLP – Decision Lifting Principle Néhány összefüggés a régi és az új elvek közt: OCP ~ DLP GOF2 ~ DLP Összefoglaló
GOF könyv
GOF1 „Program to an interface, not an implementation!” „Programozz felületre implementáció helyett!”
GOF1 megszegése 1. import java.util.*; public class MyHashSet extends HashSet{ private int addCount = 0; public boolean add(Object o){ addCount++; return super.add(o); } public boolean addAll(Collection c){ addCount += c.size(); return super.addAll(c); public int getAddCount(){ return addCount; }
GOF1 megszegése 2. import java.util.*; public class Main { public static void main(String[] args){ HashSet s = new MyHashSet(); String[] abc = {"a","b","c"}; s.addAll(Arrays.asList(abc)); System.out.println(s.getAddCount()); }
GOF1 következményei Kerülni kell az öröklést, vagy legalább Kerülni kell a super (C#-ban base) hívásokat. A kliens csak a szolgáltató felületét ismerje. azaz A szolgáltatás egy absztrakt osztály / interface mögött legyen. Minden hierarchia tetején egy absztrakt osztály legyen.
GOF2 „Favor object composition over class inheritance!” „Használj objektum összetételt öröklés helyett, ha csak lehet!” Öröklés: IS-A kapcsolat, könnyen érthető Objektum összetétel: HAS-A kapcsolat, bonyolultabb Objektum összetétel fajtái: Aggregáció: Ha meghal a gitáros, nem temetik vele a gitárját. Kompozició: Ha meghal a gitáros, vele temetik a gitárját.
Az öröklődés mindig kiváltható objektum összetétellel class A { public void m1() { Console.Write("hello"); } class B : A { public void m2() { m1(); class Program { static void Main(string[] a) { B b = new B(); b.m2(); class B { A a = new A(); a.m1();
GOF2 következményei Öröklést kerülni kell. Rugalmasabb, de nehezebben érthető lesz a kód. Lehetőséget add arra, hogy futási időben injektáljunk be felelőséget. A GOF2-nek ismerjük az előnyeit és hátrányait, de nem tudjuk, mikor kell használni!!!
Tiszta kód
SRP - Single Responsibility Principle „A class should have only one reason to change” „Az osztályoknak csak egy oka legyen a változásra” Programozás technológia elve: „A program kódja állandóan változik” Az SRP azt mondja ki, hogy egy változás hatására csak egy osztály változzon meg.
SRP következményei Kerüljük a MacsKuty típusú osztályokat: Akkor is megváltozik, ha kiderül, hogy a Kutya nem csak ugatni tud, És akkor is megváltozik, ha kiderül, hogy a Macska nem csak egerészni tud. Sok-sok unit teszt alkalmazása: TDD – Test Driven Develpment Használjuk Aspektus-Orientált Programozást pl. logolásra, jogosultság ellenőrzésre
Egy összefüggés: SRP ~ GOF1 A GOF1 megszegése gyakran az SRP megszegéséből ered: Ha egy osztály nem teljesen fedi le a fellelőség köreit, akkor egy másik osztálynak kell megvalósítani ezeket. Ehhez viszont ismernie kell az eredeti osztály megvalósítását, tehát megszegjük a GOF1-et.
OCP - Open-Closed Principle „Classes should be open for extension, but closed for modification” „Az osztályok legyen nyitottak a bővítésre, de zártak a módosításra” azaz „Az osztály hierarchia legyen nyitott a bővítésre, de zárt a módosításra”
OCP megszegésére utaló jelek Bonyolult if – else if vagy switch szerkezetek. Override használata. Csak absztrakt és hook metódusok felülírását engedi.
OCP megszegése class Alakzat { public const int TEGLALAP = 1; public const int KOR = 2; int tipus; public Alakzat(int tipus) { this.tipus = tipus; } public int GetTipus() { return tipus; } } class Teglalap : Alakzat{Teglalap():base(Alakzat.TEGLALAP){}} class Kor : Alakzat{ Kor():base(Alakzat.KOR){} } class GrafikusSzerkeszto { public void RajzolAlakzat(Alakzat a) { if (a.GetTipus() == Alakzat.TEGLALAP) RajzolTeglalap(a); else if (a.GetTipus() == Alakzat.KOR) RajzolKor(a); public void RajzolKor(Kor k) { /* … */ } public void RajzolTeglalap(Teglalap t) { /* … */ }
DLP - Decision Lifting Principle „Öröklést csak döntés kiemelésre használj!” „Use inheritance only for decision lifting!” DLP megszegésére utaló jelek: Ismétlődő if – else if szerkezetek. Ismétlődő switch szerkezetek.
DLP megszegése class Alakzat { public const int TEGLALAP = 1; public const int KOR = 2; int tipus; public Alakzat(int tipus) { this.tipus = tipus; } public int GetTipus() { return tipus; } } class Teglalap : Alakzat{Teglalap():base(Alakzat.TEGLALAP){}} class Kor : Alakzat{ Kor():base(Alakzat.KOR){} } class GrafikusSzerkeszto { public void RajzolAlakzat(Alakzat a) { if (a.GetTipus() == Alakzat.TEGLALAP) RajzolTeglalap(a); else if (a.GetTipus() == Alakzat.KOR) RajzolKor(a); public void RajzolKor(Kor k) { /* … */ } public void RajzolTeglalap(Teglalap t) { /* … */ }
DLP betartása abstract class Alakzat{ public abstract void Rajzol(); } class Teglalap : Alakzat { public override void Rajzol() { /* téglalapot rajzol */ } } class Kor : Alakzat { public override void Rajzol() { /*kört rajzol */ } class GrafikusSzerkeszto { public void RajzolAlakzat(Alakzat a) { a.Rajzol(); } class Program { public static void Main(String[] args) { Alakzat alak = new Kor(); (new GrafikusSzerkeszto ()). RajzolAlakzat(alakzat); }}
Összefüggés az OCP és a DLP közt Az OCP azt mondja, hogy kerüljük a bonyolult if – else if szerkezeteket. A DLP azt mondja, hogy kerüljük az ismétlődő if – else if szerkezeteket. Ugyanakkor meg is mondja, hogyan szüntessük meg az ismétlődést: Döntéskiemelésselű A bonyolult if – else if szerkezetek is döntéskiemeléssé degradálhatok egyszerű példányosítássá.
Összefüggés a GOF2 és a DLP közt GOF2: „Használj objektum összetételt öröklés helyett, ha csak lehet!” azaz Hacsak lehet ne használj öröklést! DLP: „Öröklést csak döntés kiemelésre használj!” A DLP pontosítja a GOF2-t, hiszen megmondja, mikor kell öröklést használni, egyébként pedig objektum összetételt kell használni.
Összefoglalás Nagyon sok tervezési alapelv van. Van amelynél tudható, hogy milyen esetben hogyan kell átalakítani a programot: GOF1, OCP, DLP. Van amelynél nem tudható, hogy kell-e használni a konkrét esetben, habár sok elméleti előnnyel jár: GOF2, SRP. Bevezettünk egy új tervezési alapelvet, a DLP-t, amely megmondja mely esetekben nem használandó a GOF2.
Köszönöm a figyelmet! Email címek: Kusper Gábor: gkusper@aries.ektf.hu Márien Szabolcs: szabolcs.marien@wit-sys.hu
Néhány összefüggés Ha úgy sértjük meg az SRP-t, hogy egy osztály nem teljesen fedi le a fellelőség köreit, akkor egy másik osztálynak kell megvalósítani ezeket. Ehhez viszont ismernie kell az eredeti osztály megvalósítását, tehát megszegjük a GOF1-et. Ha úgy sértjük meg az SRP-t, hogy egy osztály több felelőségi kört is lefed, akkor megszegjük a GOF2-öt, mert a két felelőséget szét lehetne szedni két osztályra, amit egy harmadik foghatna össze objektum összetétellel.