Iznuđeno duplo nasleđivanje

Iz života programera.

Postavka

Potrebno je izvršiti nekakav proces verifikacije koji se aktivira na dva mesta u sistemu. Pravim interfejs, jer postoje različiti načini verifikacije (final je uklonjen iz primera radi čitljivosti):

public interface Verify {
	VerifyResult verifyOnFoo(Task task, Employee employee);
	VerifyResult verifyOnBar(Task task, Employee employee);
}

Procesi verfikacije su jednostavni algoritmi koji proveravaju validnost akcija foo i bar. Bitno je da se algoritmi verifikacije služe istim domenskim jezikom: uvodim metode approve(), deny() i slične koje bi se koristile u implementacijama. Ovaj mali domenski jezik bi se mogao izvući iz bekenda u (java)skriptoliki run-time jezik, čime bi hardkodovan algoritam postao deo konfiguracije, zamenljive u runtime (jedan od vlažnih snova vlasnika projekta.) Upadica: šta je sa dinamičkom zamenom koda u Javi? Ubijena je konetejnerima.

Iako su sve domenske metode čiste, stavljam ih u baznu apstraktnu klasu:

public abstract class BaseVerification {
	protected ApprovalStatus approve() {...}
	protected ApprovalStatus reject() {...}
	protected ApprovalStatus deny(String message) {...}
	// ...
}

Razlozi za baznu klasu:

Primer korišćenja:

public class ClientAVerification
    extends BaseVerification implements Verify {

	public VerifyResult verifyOnFoo(...) {
		if (uslov()) {
			approve();
		}
		deny("Ain't gonna happened.");
	}

	private boolean uslov() {}
	// ...
}

Šta sam pogrešio? Ukratko: favorizovao sam nasleđivanje zarad (upitno) čitljivijeg koda. OK, grešku sam svesno napravio, znam da postoji, lokalizovana je, idemo dalje.

S druge strane, ovo je potpuno validan slučaj OOP nasleđivanja. Zar ne?

Nasleđivanje nasleđivanja

Pokazuje se da obe metode za verifikaciju (verifyOnFoo i verifyOnBoo) rade slično; mogu se parametrizovati. Drugim rečima, imamo novo zajedničko ponašanje za implementacije. Mesto takvom kodu je u - opet, baznoj klasi.

Međutim, ovaj kod ne može da završi u BaseVerification iz dva razloga. Nove metode nisu deo domenskog jezika, te nema smisla mešati ih. Drugi razlog je što BaseVerification ne implementira Verify interfejs. Sve i da hoćemo da metode đuture gurnemo u jednu baznu klasu, ne bi mogli da to izvedemo - osim da ne učinimo da i ona implementira Verify interfejs, što je besmisleno: jezik za verifikaciju postaje sam za sebe verifikacija.

Način da se iščupamo iz ove situacije je pravljenje nove abstraktne klase i, time, povećanje hijerarhije:

public abstract class CommonVerification extends BaseVerification implements Verify {

	protected void commonVerify(
	    Task task, Employee employee,
		  Supplier<Boolean> uslov
	) {
		if (uslov.get()) {
			approve();
		}
		deny("Ain't gonna happened.");
	}
}

Odjednom sam završio sa hijerarhijom od dve podklase.

Šta je problem?

Problem su višestruki razlozi za nasleđivanjem. Da se podsetimo, nasleđivanje je samo redefinisanje funkcija i varijabli u podskupu. U mom slučaju sam imao OOPotrebu da 1) definišem zajedničke metode za DSL validaciju i 2) izdvojim uočeno zajedničko ponašanje. Namere su ortogonalne: nisu kompatibilne, tiču se drugačijih ciljeva. OOP način da ih rešim je forsiranje hijerarhije.

Da se ispravim: “višestruki razlozi za nasleđivanjem” nisu problem. Problem je mehanizam razrešavanja potrebe: nasleđivanje. Java utiče na naš mentalni aparat i čino da olako donosimo odluke o nasleđivanju - jednoj od najčvršćih veza u OOP, i, dodao bih, koja unosi dovoljno nedoumica se je upotreba upitna.

Razrešenje

DSL jezik je izvučen u funkcije. Time je uklonjen jedan stepen hijerarhije; ostaje samo “lagana” bazna klasa za zajedničkim ponašanjem. I ona je mogla da nestane; no s obzirom da sadrži samo jednu metodu, dozvoljavam joj da poživi… još malo.

Ništa novo: kompozicija pre, pre, pre i mnogo pre nasleđivanja.

🧧
Nisam definisan svojim stavovima. Stavove usvajamo, menjamo, nadograđujemo, ali oni ne čine nas same. Manje je važno da li se slažemo,
koliko da se razumemo.
> ČASTI KAFU <