OOP, Klassen, Unterklassen, Vererbung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
TheBen
User
Beiträge: 4
Registriert: Dienstag 2. August 2011, 19:42

Hallo liebe Python-Gemeinde,

ich programmiere erst seit ein paar Tagen mit Python. Meine Vorkenntnisse beschränken sich auf wenige VBA-Programme mit geringem Umfang. OOP ist für mich gänzlich neu, alles Bisherige war prozeduales Programmieren.

Eine grundsätzliche Frage, die sich bisher ergeben hat bezieht sich auf den Umgang mit Klassen, die andere Klassen benutzen sollen.
Mal angenommen, ich wäre Unternehmer und möchte Außenbüros gründen, könnte die Vorlage für eine Datenbank ja so aussehen:

Code: Alles auswählen

#! /usr/bin/python
# -*- coding: utf-8 -*-

class Mitarbeiter:
	def __init__(self,name):
		self.name=name
	def pMA(self):
		print "\t\t"+self.name

class Geschaeftsfuehrer:
	def __init__(self,name):
		self.name=name
	def pGF(self):
		print "\t\t"+self.name

class Aussenbuero:
	def __init__(self):
		self.MA1=Mitarbeiter("Harald E.")
		self.MA2=Mitarbeiter("Gerd F.")
		self.GF1=Geschaeftsfuehrer("Albert S.")

AB_Berlin=Aussenbuero()
print "\nAußenbüro Berlin:"
print "\tGeschäftsführer:\t"
AB_Berlin.GF1.pGF()
print "\tMitarbeiter:"
AB_Berlin.MA1.pMA()
AB_Berlin.MA2.pMA()
print
was zu folgender Ausgabe führt:

Außenbüro Berlin:
Mitarbeiter:
Harald E.
Gerd F.
Geschäftsführer:
Albert S.
Da hier nur die Hauptklasse auf zwei Unterklassen zugreift, bleibt das Ganze sehr übersichtlich und der Methodenaufruf zum Ausgeben auch noch recht einfach (z. B. AB_Berlin.G1.pGF()). Was ist aber nun, wenn ich die Klasse PKW habe, die auf die Klasse Antriebsstrang zurückgreift, die wiederum auf die Klasse Motor zurückgreift, die wiederum auf die Klasse Kurbelwelle zurückgreift, wobei die Kurbelwelle das Attribut Gewicht hat, welches ich im Hauptprogramm ausgeben möchte. Nach obiger Methode müsste ich dann das Attribut dann zum Beispiel über Opel.AntriebsstrangNormal.Motor75kW.KWgeschmiedet.printGewicht() ausgeben, was mir aber doch recht unhandlich erscheint. Ich habe mir auch verschiedene Beispiele hinsichtlich Vererbung angeschaut, aber bei Vererbung geht der Bezug verloren, d.h wenn ich Kurbelwelle als Basisklasse für Motor, Motor als Basisklasse für Abntriebsstrang usw. nehme, kann ich zwar mit Opel.printGewicht() das Attribut Gewicht ausgeben, nur fehlt dem dann jede offensichtliche Verknüpfung zur Kurbelwelle, außerdem dürfte ich die Methode printGewicht in keiner anderen Klasse verwenden.

Für ein paar prinzipielle Tipps wäre ich sehr dankbar. Dürfen gerne auch Literaturhinweise u.ä. sein.

Grüße
TheBen
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

was mich verwirrt ist, dass du in dem einleitenden Text von OOP und von Datenbank sprichst...Für ein DB modelliert man nämlich u.u. anders als man das bei Klassen tun würde. Bzw. man nimmt ein ORM wie SQLAlchemy, der mappt dann eine relationale DB auf Klassen.

Was ist denn das eigentliche Anliegen?

Gruß, noisefloor
deets

@Ben

Dafuer gilt das Gesetz von Demeter: http://de.wikipedia.org/wiki/Gesetz_von_Demeter

Um's mal salopp zu sagen: wenn ich wissen will, was du wiegst, stelle ich dich auf eine Wage. Ich hacke dir nicht nacheinander alle Beine & Arme und den Kopf ab, um deren Gewichte zu summieren.

Das heisst in deinem konkreten Fall: ein Auto besteht aus Teilen, und jedes Teil kennt sein Gewicht. Und das besteht aus dem Eigengewicht (kann 0 sein, wenn es sich nur um eine "organisatorische Einheit" handelt, wie zB Antriebstrang) plus dem Gewicht aller Teile, die es enthaelt.

Deine Aussenstellen-Modellierung ist auch eher ungeschickt. Denn eine Aussenstelle hat ja dynamische Mitarbeiter. Es mag immer einen GF geben, aber das wuerde ich anders modellieren, grob so:

Code: Alles auswählen


class Aussenstelle(object):


     def __init__(self):
           self.mitarbeiter = []


     @property
     def GF(self):
           return [m for m in self.mitarbeiter if isinstance(m, Geschaeftsfuehrer)][0] # falls nur einer, kann man auch anders machen

Generell gilt: wann immer du dich bemuessigt fuehlst, Variablen zu numerieren, benutzt du keine bzw. die falsche Datenstruktur.
Benutzeravatar
pillmuncher
User
Beiträge: 1530
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Du suchst das Law of Demeter. Zuerst solltest du allerdings das Problem verstehen, das OO lösen soll, und das ist IMO nicht, wie man statische Datenstrukturen hierarchisch organisiert. OO löst zuerst einmal das Problem der Fallunterscheidung anhand des Typs:

Code: Alles auswählen

class Auto(object):
    pass

class Fahrrad(object):
    pass

def fahr_los(fahrzeug):
    if type(fahrzeug) is Auto:
        print 'drehe den Zündschlüssel'
        print 'trete auf die Kupplung'
        print 'lege den ersten Gang ein'
        print 'trete aufs Gaspedal und gehe von der Kupplung'
    elif type(fahrzeug) is Fahrrad:
        print 'steige auf'
        print 'fang an zu treten'

def bleib_stehen(fahrzeug):
    if type(fahrzeug) is Auto:
        print 'trete auf die Kupplung'
        print 'trete auf die Bremse'
    elif type(fahrzeug) is Fahrrad:
        print 'höre auf zu treten'
        print 'drück auf die Bremse'

fahrzeuge = [Auto(), Fahrrad()]

import random

mein_fahrzeug = fahrzeuge[random.randint(0, 1)]

fahr_los(mein_fahrzeug)
bleib_stehen(mein_fahrzeug)
Angenommen, du willst nun einen neuen Fahrzeugtyp hinzufügen, dann muss nicht nur der neue Typ definiert werden, sondern auch die bestehenden Funktionen fahr_los() und bleib_stehen() geändert werden. Hier zum Vergleich die OO-Version:

Code: Alles auswählen

class Fahrzeug(object):
    def fahr_los(self):
        pass
    def bleib_stehen(self):
        pass

class Auto(Fahrzeug):
    def fahr_los(self):
        print 'drehe den Zündschlüssel'
        print 'trete auf die Kupplung'
        print 'lege den ersten Gang ein'
        print 'trete aufs Gaspedal und gehe von der Kupplung'
    def bleib_stehen(self):
        print 'trete auf die Kupplung'
        print 'trete auf die Bremse'

class Fahrrad(Fahrzeug):
    def fahr_los(self):
        print 'steige auf'
        print 'fang an zu treten'
    def bleib_stehen(self):
        print 'höre auf zu treten'
        print 'drück auf die Bremse'

fahrzeuge = [Auto(), Fahrrad()]

import random

fahrzeug = fahrzeuge[random.randint(0, len(Fahrzeuge)-1)]

fahrzeug.fahr_los()
fahrzeug.bleib_stehen()
Statt die genannten Funktionen zu ändern, braucht man hier nur den neuen Typ zu definieren:

Code: Alles auswählen

class Schiff(Fahrzeug):
    def fahr_los(self):
        ''' hierhin kommt der Code zum losfahren '''
    def bleib_stehen(self):
        ''' hierhin kommt der Code zum stehen bleiben '''
Dazu muss man das Hauptprogramm anpassen, darum kommt man nicht herum:

Code: Alles auswählen

fahrzeuge = [Auto(), Fahrrad(), Schiff()]
Das ist IMO der ganze Witz der OO-Programmierung.
In specifications, Murphy's Law supersedes Ohm's.
deets

@pillmuncher

Ich wuerde ja immer erst bremsen + dann aufhoeren zu treten oder die Kupplung bedienen... SCNR ;)
problembär

pillmuncher hat geschrieben:Du suchst das Law of Demeter. ...
Ist ja schon schön, daß man OOP vielleicht doch noch begreifen lernt, wenn man sich hier durch die Themen hangelt. ;)
Die andere Sache war dann noch MVC.
Beides OOP-Prinzipien, die ich so nicht in meinen Büchern gefunden habe, obwohl sie essentiell wichtig sind.

Demeter kannte ich bisher wie gesagt nicht. Mir war schon klar, daß Objekte möglichst gekapselt sein sollten, nur habe ich das bisher nie durchhalten können. Stattdessen habe ich mir damit geholfen, ein "großes, haariges Objekt" zu bauen, über das dann alle Klassen auf alle anderen zugreifen konnten. Das war nicht Sinn der Sache, aber es funktionierte wenigstens. Ob die Kapselung mit Demeter (oder Demeter?) auch funktioniert, das werde ich erst ausprobieren müssen.

Gruß
Benutzeravatar
pillmuncher
User
Beiträge: 1530
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@TheBen: Dein Programm könnte ungefähr so aussehen:

Code: Alles auswählen

class Person(object):
    def __init__(self,name):
        self.name = name
    def print_name(self):
        print '\t\t' + self.name

class Aussenbuero(object):
    def __init__(self):
        self.geschaeftsfuehrer = Person('Albert S.')
        self.mitarbeiter = [Person('Harald E.'), Person('Gerd F.')]
    def print_geschaeftsfuehrer(self):
        self.geschaeftsfuehrer.print_name()
    def print_mitarbeiter(self):
        for each in self.mitarbeiter:
            each.print_name()

AB_Berlin = Aussenbuero()
print '\nAußenbüro Berlin:'
print '\tGeschäftsführer:\t'
AB_Berlin.print_geschaeftsfuehrer()
print '\tMitarbeiter:'
AB_Berlin.print_mitarbeiter()
print
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich würde weder Mitarbeiter noch GF "hard coden" - dann müsste man bei Personalfluktuation ja jedes Mal an den Code...

Code: Alles auswählen

...
class Aussenbuero(object):
    def __init__(self):
        self.geschaeftsfuehrer = ''
        self.mitarbeiter = []
    def add_mitarbeiter(mitarbeiter):
        self.mitarbeiter.append(mitarbeiter)
    def print_geschaeftsfuehrer(self):
        self.geschaeftsfuehrer.print_name()
    def print_mitarbeiter(self):
        for each in self.mitarbeiter:
            each.print_name()
...
Gruß, noisefloor
TheBen
User
Beiträge: 4
Registriert: Dienstag 2. August 2011, 19:42

Hey Leute,

vielen Dank für das rege Feedback. Ich werde es morgen auswerten und melde mich bei Bedarf wieder.

Grüße
TheBen
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

pillmuncher hat geschrieben:

Code: Alles auswählen

if type(fahrzeug) is Auto:
[...]
Mal davon ab, dass deine Funktion offenbar Exemplare von Typen/Klassen und nicht etwa die Klassen selbst übergeben bekommen soll und man deshalb dementsprechend `isinstance()` benutzen sollte, empfiehlt sich für eine reine Typprüfung das Built-in `issubclass()`. Das ist flexibler, weil es nämlich auch mit abgeleiteten Klassen umgehen kann.

PS: Ich weiß, dass der Fokus deines Beitrags woanders lag. ;)
Antworten