OOP - Hab ichs richtig verstanden?^^

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
Astorek
User
Beiträge: 72
Registriert: Samstag 24. Januar 2009, 15:06
Kontaktdaten:

Hi @ all,

als jemand, der über 10 Jahre lang ausschließlich prozedural programmiert hat, brauche ich ehrlichgesagt jetzt etwas Bestätigung, ob meine Interpretation von OOP einigermaßen alltagstauglich ist. Die Grundzüge zur OOP-Programmierung habe ich mir von dieser Tutorialreihe abgeschaut (dort geht es zwar um C++ , aber im Kontext von OOP sollte das eigentlich großteils egal sein, würde ich meinen^^)...

Basierend auf das Tutorial habe ich ein relativ kleines Beispiel geschrieben: Ich wollte eine Art (sehr kleines) GUI-Framework bauen - und ich wollte einfach mal so dreist fragen, ob man diesen Code als OOP-tauglich bzw. generell brauchbar bezeichnen kann^^. Mir gehts dabei wirklich nur um OOP ansich und wollte sicherheitshalber einfach im Forum nachfragen, bevor ich mit einer eventuell falschen Herangehensweise weiterprogrammieren würde^^. (Aktuell hat das Programm noch keinen wirklichen Sinn, es dient nur dazu, mir selbst OOP beizubringen...)

Quellcodes (hab ich in Python 2.7 und 3.2 erfolgreich getestet):
Direktdownload als ZIP

pastebin main.py
pastebin imenu.py
pastebin icreate.py
pastebin menu_label.py
---

Kurze Erklärung zum Quellcode, wie ich mir das vorgestellt hätte: Alle möglichen GUI-Elemente (z.B. ein Textlabel), die man darstellen kann, werden in der "main" erstmal in "imenu" registriert. "imenu" kümmert sich darum, GUI-Elemente zu erstellen (Methode "Create"), und Methoden u. Attribute für alle GUI-Elemente zu setzen (Methoden "show" und "hide_all"). Wenn mittels "imenu" ein neues GUI-Element erstellt wird, gibts als Rückgabewert eine Instanz von "icreate", dass die Optionen des jew. GUI-Elements verwaltet und deren Methoden aufruft. Ein Beispiel-GUI-Element wär "menu_label"...

Wie ich mir das mit den GUI-Elementen gedacht habe: Beim Programmieren kann man so problemlos neue hinzufügen (z.B. eine "button.py", welche Buttons zeichnet), die man lediglich in der imenu.py registrieren muss (was in der main.py geschieht). Die GUI-Elemente selbst haben einerseits einen eindeutigen Bezeichner (sinnigerweise den Variablennamen "NAME"), mit dem man auf sie zugreifen kann und definierte Funktionen (im Beispiel hier "show", aber man kann z.B. später problemlos sowas wie "mouseover" o.Ä. hinzufügen). Den Zugriff auf die GUI-Elemente selbst findet ausschließlich über "icreate" statt, sodass ich bei zusätzlichen Methoden der GUI-Elemente nur diese Klasse erweitern muss.

Tjoar, wie gesagt: Ich würde gerne wissen, ob das eine für Python-Verhältnisse einigermaßen brauchbare Form von OOP ist, oder ob ich da irgendwo viel zu kompliziert gedacht hatte^^. Schonmal im Voraus Danke fürs Überfliegen meines Quelltextgewurschtels^^...
BlackJack

@Astorek: Für welche Programmiersprache ein OOP-Tutorial gedacht ist, muss nicht unbedingt egal sein. OOP ist ja letztendlich „nur” ein Konzept, eine Idee. Die Spracheigenschaften zur Unterstützung dieser Idee und deren Verwendung können dabei von Sprache zu Sprache teilweise stark variieren. Zu C++ hat der Erfinder des Begriffs OOP, Alan Kay, mal gesagt, dass das nicht das ist, was er sich unter OOP vorgestellt hatte.

Zum Quelltext: Das ist IMHO kein (gutes) OOP. Das ist eine komplizierte Art Module hinter Exemplaren einer Klasse (`Icreate`) zu verstecken, die man nur mit einer unnötigen Fabrik (`Imenu`) erstellen kann. Und das Ganze ist auf zu viele Module verteilt. In Python ist es nicht üblich eine Klasse pro Modul zu haben. Das würde die Organisationseinheit Modul weitestgehend überflüssig machen. Bei Dir sind `Imenu` und `Icreate` eigene Module und zwangsweise jedes Widget.

Dadurch dass Du diesen „Widget-Implementierung als Modul wird in einem Exemplar einer Klasse gewrappt”-Ansatz gewählt hast, kann man auch keine Widgets implementieren, die andere erweitern. Stichwort Vererbung. *Das* würde man eher machen wenn man alle Widgets um allgemeine Methoden erweitern möchte — die Methode einer Basisklasse hinzufügen, von der alle Widgets erben.

Zur Namensgebung: Was soll das 'I' bei `Imenu` und `Icreate`? Warum heisst `Imenu` eigentlich Menü? Das müsste eigentlich eher `WidgetFactory` *und* `GUI` heissen. Womit klar wird, dass dort zwei Sachen vermischt werden: Verwalten von „Klassen” *und* Exemplaren.

Klassen repräsentieren „Dinge” und deshalb sollte man auch entsprechende Namen wählen. „Create” bezeichnet kein Ding, sondern eine Tätigkeit — das ist ein Name für eine Funktion oder Methode. Deine `Icreate`-Exemplare stehen für GUI-Widgets, also wäre `Widget` ein passenderer Name.

Klassischer Ansatz wäre zum Beispiel folgendes:

Code: Alles auswählen

class Widget(object):
    
    DEFAULT_OPTIONS = dict()
    
    def __init__(self, parent, options=None):
        self.parent = parent
        self.options = dict(self.DEFAULT_OPTIONS)
        self.hidden = False
        if options is not None:
            self.set_options(options)
        if self.parent is not None:
            self.parent.register(self)
    
    def __str__(self):
        return '<%s 0x%x %r>' % (
            self.__class__.__name__, id(self), self.options
        )
    
    def set_options(self, options):
        for name, value in options.iteritems():
            if name not in self.options:
                raise ValueError('unknown option %r' % name)
            self.options[name] = value
    
    def show(self, surface):
        raise NotImplementedError


class ContainerWidget(Widget):
    def __init__(self, parent, options=None):
        Widget.__init__(self, parent, options)
        self.children = list()
    
    def __iter__(self):
        return iter(self.children)
    
    def register(self, widget):
        self.children.append(widget)
    
    def show(self, surface):
        if not self.hidden:
            for widget in self:
                widget.show(surface)
    
    def set_hidden(self, value):
        for widget in self:
            widget.hidden = value


class GUI(ContainerWidget):
    
    def __init__(self, surface, options=None):
        self.surface = surface
        ContainerWidget.__init__(self, None, options)
    
    def show(self, surface=None):
        if surface is None:
            surface = self.surface
        ContainerWidget.show(self, surface)


class Label(Widget):

    DEFAULT_OPTIONS = {
        'pos' : (0, 0),
        'font' : None,
        'size' : 12,
        'text' : ''
    }
    
    def show(self, surface):
        if not self.hidden:
            print self


def main():
    surface = None
    menu = GUI(surface)
    
    print 'Erster Test'
    print '-----------'
    label = Label(menu, {'pos': (1, 1)})
    label.show(surface)
    print '-----------'

    print 'Zweiter Test' 
    print '------------'
    another_label = Label(menu, {'pos': (2, 2), 'size': 15})
    another_label.hidden = True
    
    _a_label_again = Label(menu, dict(pos=(3, 3), text='bla'))
    menu.show()
    print '------------'

    print 'Dritter Test'
    print '------------'
    menu.set_hidden(False)
    
    menu.show()


if __name__ == '__main__':
    main()
Es gibt eine (abstrakte) `Widget`-Basisklasse wo alles rein kommt was für *alle* Widgets gilt. Eine (abstrakte) `ContainerWidget`-Basisklasse die ein `Widget` ist und zusätzlich Funktionalität für alle Widgets bietet, welche andere Widgets beinhalten. Und zwei konkrete Widget-Typen: `GUI` und `Label`.

`GUI` ist ein spezieller Container, weil es als oberstes Exemplar in einer Hierarchie gedacht ist.

Mindestens alles bis dahin würde ich im selben Modul zusammenfassen, denn diese Klassen bilden zusammen die Grundlage auf der andere Widgets aufbauen. `Label` könnte man mit anderen Basis-Widgets, die man von jedem GUI-Toolkit erwartet, in ein eigenes Modul stecken — ich würde die Basis-Widgets aber wohl auch mit in das Modul stecken wo `Widget` definiert ist.
Astorek
User
Beiträge: 72
Registriert: Samstag 24. Januar 2009, 15:06
Kontaktdaten:

Zunächst mal: Sorry, dass ich mich erst jetzt wieder melde.

Gleich darauf: Wirklich vielen, vielen Dank für deine ausführliche Beschreibung, die mir sehr geholfen haben! :) Ich hab etwas gebraucht, das zu verstehen ("Prozedurales Denken" kriegt man leider wirklich nicht so schnell aus dem Kopf raus^^), aber nach gefühlt 100x ansehen und des Verstehen-Wollens hat es wohl endlich *Klick* in meinem Kopf gemacht^^.

Eine Sache verstehe ich allerdings noch nicht wirklich: Weshalb existiert die GUI-Klasse, bzw. welchen Vorteil bekomme ich hiervon? Anders gefragt: Was spräche dagegen, direkt die "ContainerWidget"-Klasse anzusprechen? Das ist mir ehrlichgesagt noch nicht klargeworden...
BlackJack

@Astorek: Die enthält das `surface` auf dem alles dargestellt wird. Wenn man die weg lässt, muss man das bei `show()` jedes mal als Argument übergeben. Wie beim ersten Test mit dem `Label`. Kann man natürlich auch machen.
Antworten