Wykorzystanie SOLID w abstrakcji lasu

Jako inżynier posiadający wykształcenie związane z lasem czuję się uprawniony żeby odnieść swoją wiedzę programistyczną do zagadnień związanych z drzewostanem. Oczywiście informatyzacja dotarła już dawno do leśnictwa a jej głównym zadaniem jest wsparcie w zakresie zarządzania zasobami leśnymi co sprowadza się do obliczeń zapasu i tutaj bardzo wiele pracy jest wykonanej na poczet inwentaryzacji zasobów przy pomocy bardzo zaawansowanych technologii ,m.in fotogrametrii oraz lidaru. Tym wstępem chciałem uprzedzić ludzi z branży IT że leśnictwo jest wbrew pozorom branżą korzystającą w bardzo dużej mierze z osiągnięć technologii. Z kolei dla leśników, przykłady pokazane tutaj mogą okazać się trywialne ale chodzi mi tutaj bardziej o dydaktykę niż o praktykę, więc będę kierował się uproszczeniami.

W tym wpisie zaprezentuję metody SOLID w programowaniu obiektowym odnosząc się do abstrakcji lasu. Dokonamy wirtualizacji lasu a konkretnie drzewostanu do postaci obiektowej. No więc weźmy takie drzewo i stwórzmy dla niego klasę….

1. S- czyli Single responsibility.

Zasada jednej odpowiedzialności. Według tej zasady, w projektowaniu klasy należy kierować się tym aby klasa ograniczała się do jednej odpowiedzialności. Jak to się ma do klasy drzewo? Drzewo w ujęciu użytkowym to obiekt którego wartością jest jego drewno. Dlatego żeby móc jego wartość wycenić potrzebujemy znać jego parametry.

class Tree:
   def __init__(self, dbh, height,age):
       self.dbh=dbh
       self.height=height
       self.age=age

   def printDbh(self):
       print "DBH is:"+str(self.dbh)

   def getVol(self):
       print "I am calculating tree volume from dbh and height "

W powyższym przykładzie inicjujemy klasę podając parametry drzewa:

  • DBH (diameter breast height) czyli po polsku pierśnica to średnica pnia na wysokości 1,3 m (czyli na wysokości piersi ,stąd nazwa).
  • height -wysokość drzewa
  • age -wiek drzewa

Stworzono metodę do pokazania pierśnicy (dbh) oraz metodę do obliczenia miąższości czyli “ile kubików” ma drzewo. Jednak w praktyce istnieje kilka metod obliczania miąższości, które bazują na różnych parametrach drzewa. Można na przykład obliczyć posługując się jedynie wiekiem i pierśnicą gdyż czasem pomiar wysokości nie jest możliwy. Istnieją również parametry związane z siedliskiem na którym to drzewo rośnie i to ma wpływ na miąższośc drzewa. Biorąc to pod uwagę lepiej jest wydzielić klasę do obliczania miąższości:

class Tree:
   def __init__(self, dbh, height,age):
       self.dbh=dbh
       self.height=height
       self.age=age

   def printDbh(self):
       print "DBH is:"+str(self.dbh)

class VolumeCalculation:
   def getVolume1(self,dbh, height):
       print "I am calculating tree volume using dbh and height "

   def getVolume2(self, dbh, siteIndex):
       print "I am calculating tree volume using dbh and site index "

W tym przypadku możemy obliczyć miąższość na różne sposoby biorąc też pod uwagę zmienne niezależne od klasy drzewo.

2. O – Open for Extension, Closed for Modification.

Możemy klasę rozszerzyć i rozwijać ją w kierunku specjalizacji a nie modyfikować istniejącej gdyż może to powodować konflikt. Na naszym przykładzie, chcemy dodać cechę opisującą jakość pnia, np tzw zgniliznę korzeniową. Jej obecność powoduje spadek jakości.

class Tree:
   def __init__(self, dbh, height,age):
       self.dbh=dbh
       self.height=height
       self.age=age
   rot_extent=0
   def setRotExtent(self,rot_extent):
       self.rot_extent=rot_extent;
       print "rot extent is: "+str(self.rot_extent)
 

Dodaliśmy metodę gdzie możemy zdefiniować ile tej zgnilizny jest. Ale ustawienie tej zmiennej nie ma sensu dla gatunków gdzie ta choroba nie występuje, tak jak na przykład u drzew liściastych. Ponieważ zachodzą istotne różnice w określaniu cech ilościowo jakościowych pomiędzy różnymi gatunkami, możemy rozszerzyć klasę do drzew iglastych /liściastych a może lepiej bezpośrednio do gatunków.

class Tree:
   def __init__(self, dbh, height,age):
       self.dbh=dbh
       self.height=height
       self.age=age

class Fir(Tree):
   def setRotExtent(self,rot_extent):
       self.rot_extent=rot_extent;
       print "rot extent is: "+str(self.rot_extent)


fir1=Fir(24,15,80)
fir1.setRotExtent(34)

Po poprawieniu rozszerzyliśmy klasę drzewo do klasy jodła(Fir) gdzie została dodana metoda określająca zakres zgnilizny pnia.

3. L- Liskov Substitution.

Zasada podstawienia Liskov’a.Dotyczy zachowania zgodności interfejsów tak żeby metody klasy bazowej mogły zastępować metody klasy pochodnej. Poruszając się w naszej konwencji, spójrzmy na przykład gdzie implementujemy metodę zwracającą pierśnicę drzewa. Otóż dokonując pomiaru drzewa,uwzględniamy drewno i korę. Dla obliczenia naszej miąższości potrzebujemy zatem średnicę bez kory. Oto przykład gdzie należy zrobić to poprawnie:

class Tree:
   def __init__(self, dbh, height,age):
       self.dbh=dbh
       self.height=height
       self.age=age
   def getDbh(self):
       return self.dbh

class Fir(Tree):
   def setRotExtent(self,rot_extent):
       self.rot_extent=rot_extent;
       print "rot extent is: "+str(self.rot_extent)
   def getDbh(self):
       bark=0.02
       return Tree.getDbh(self)-bark

W dziedziczonej klasie używamy tej samej nazwy metody, różnica polega na tym że dodajemy zmienną grubości kory o którą pomniejszamy wynik.

4. I – Interface Segregation.

Segregacja interfejsów. Powinniśmy dążyć do minimalizacji interfejsów po to aby implementowały jedynie istotne klasy. Duże interfejsy powinny być dzielone na mniejsze.Możemy sobie wyobrazić że zajmujemy się gospodarką leśną która obejmuje różne aspekty dotyczące hodowli lasu. W niniejszym przykładzie skorzystam z języka PHP który implementuje interfejsy:

public interface ForestManager {
    void plantingForest();
    void forestTreatment();
    void cuttingForest();
}

Interfejs zarządzanie lasu obejmuje bardzo różne zadania i lepiej by było żeby rozbić odpowiedzialność za te zadania. Dlatego stworzymy 3 osobne interfejsy:

public interface forestPlanter {
    void plantingForest();
}
 
public interface forestQualityMaker {
    void forestTreatment();
}
 
public interface harvester {
    void cuttingForest();
}

I ostatecznie tworzymy 2 klasy które implementują interfejsy:

public class forestRegeneration implements forestPlanter {
 
    public void plantingForest(); {
        //we can plant manually , with machine or support the natural regeneration
    }

}

public class silviculture implements forestQualityMaker, harvester {
 
    public void forestTreatment() {
        //early thinnings, late thinnings
    }
 
    public void cuttingForest() {
        //single tree selection, clearcut
    }
}

No więc w praktyce dzięki tej segregacji zadania hodowli lasu podzielono pomiędzy specjalistów (klasę) odnowienia lasu którzy posiadają metodę sadzenia lasu (i wszystkie zasoby ludzkie i sprzętowe), a specjalistów od hodowli lasu którzy za pomocą cięć dokonują zabiegów takich jak trzebieże, a potem używają metody wyrębu lasu (a ta metoda może przyjmować różne formy).

5. D – Dependency inversion.

Odwrócenie zależności. Polega ona na używaniu interfejsu polimorficznego wszędzie tam gdzie jest to możliwe, szczególnie w parametrach funkcji. W naszym praktycznym przykładzie z lasu odniesiemy się do drzewostanu czyli określonego obszaru populacji różnych gatunków drzew:

class Stand:
   def __init__(self):
       self.spruce = []
       self.pine = []
       self.fir = []

   def addSpruce(self, swierk):
       self.spruce.append(swierk)
   def addPine(self, sosna):
       self.pine.append(sosna)
   def addFir(self, jodla):
       self.fir.append(jodla)

Dla każdego gatunku musimy zaimplementować metodę aby dodawać gatunek. Tutaj jest ich 3, ale może być ich więcej. Skorzystamy z zasady D:

class Tree(object):
   pass


class Stand:
   def __init__(self):
       self.trees = []

   def addTree(self, species):
       self.trees.append(species)

class Spruce(Tree):
   def __init__(self):
       print "added Spruce"

class Pine(Tree):
   def __init__(self):
       print "added Pine"

class Fir(Tree):
   def __init__(self):
       print "added Fir"

if __name__ == "__main__":
   st=Stand()
   st.addTree(Spruce())
   st.addTree(Fir())
   st.addTree(Pine())
   st.addTree(Pine())

Po uruchomieniu skryptu otrzymujemy:

added Spruce
added Fir
added Pine
added Pine

Jeśli pojawi się nowy gatunek którego nie było na liście, będzie łatwiej zaktualizować listę klas oraz użycie jej w klasie stand.

Wykorzystanie SOLID w abstrakcji lasu

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *