@Chris71: Ob man Klassen oder Funktionen nimmt, hängt nicht von irgendwelchen Themen ab.
Eine Funktion repräsentiert eine einzelne Berechnung, im allgemeinsten Sinne. Etwas, das der Rechner tun soll und das ich als logische Einheit betrachte. Funktionen sollten so programmiert werden, dass klar ist, ob sie den Zustand ihrer Laufzeitumgebung modifizieren. Eine Funktion, die etwas in eine Datei schreibt, sollte nicht
check_something() oder
read_the_other_thing() heißen, sondern eher
write_the_stuff(). Der Name sollte im Imperativ stehen um die Funktion als Befehl kenntlich zu machen. Funktionen, die Werte berechnen, sollten keine Seiteneffekte haben, dh., sie sollten den Zustand ihrer Laufzeitumgebung nicht ändern. Der Name einer solchen Funktionen sollte klar machen, dass es sich um eine Abfrage handelt. In anderen (Nicht-Objekt-Orientierten) Sprachen als Python verwendet man gerne
get_ als Präfix, also
get_state(something),
get_total_price(order),
get_customer_address(customer_id). In Python dagegen würde man sich Klassen definieren. Diese modularisieren den Zustand über eine Menge von Daten. Die Klassen sollte man sich allerdings nicht als bloße Datentöpfchen vorstellen, in die man zusammengehörige Daten reinwirft, um sie dann von außen ab und zu umzurühren, zu vermehren oder zu vermindern. Sondern man sollte sich eine Klasse als Beschreibung von Objekten (den sog. Exemplaren oder Instanzen) vorstellen, wobei diese Objekte es sind, die sich in je irgendeinem Zusand befinden, den sie einkapseln. zB. en File-Objekt. Es hat die logischen Zustände
geöffnet oder
geschlossen. Ein File-Objekt kann sich zu jedem Zeitpunkt nur in genau einem dieser Zustände befinden, dh. nie zugleich geöffnet und geschlossen sein. Wenn es sich im geöffnet-Zustand befindet, kann man Daten lesen oder schreiben, je nachdem, ob man die Datei zum Lesen oder Schreiben geöffnet hat. Im geschlossen-Zustand kann man weder lesen noch schreiben. Als Klasse könnte das ungefähr so aussehen:
Code: Alles auswählen
class File:
def __init__(self, name):
self.name = name
self._is_open = False
self._mode = None
def open(self, mode):
if self._is_open:
raise Exception('file already open')
# <-- hier sollte das tatsächliche Öffnen der Datei stattfinden
self._mode = mode
self._is_open = True
def close(self):
if not self._is_open:
raise Exception('file is not open')
# <-- hier sollte das tatsächliche Schließen der Datei stattfinden
self._mode = None
self._is_open = False
def read(self):
if not self._is_open:
raise Exception('file is not open')
if not self._mode == "r":
raise Exception('file was not opened in read mode')
# <-- hier sollte das tatsächliche lesen aus der Datei stattfinden
return ...
def write(self, data):
if not self._is_open:
raise Exception('file is not open')
if not self._mode == "w":
raise Exception('file was not opened in write mode')
# <-- hier sollte das tatsächliche Schreiben in die Datei stattfinden
Die tatsächliche Schnittstelle von Dateien ist natürlich etwas komplexer. Mit Schnittstelle meine ich die Gesamtheit aller auf Dateiobjekten definierten Methoden zusammen mit ihren Parametern. Jedes Objekt, das dieselbe Schnittstelle definiert, kann dann an jeder Stelle verwendet werden, wo ein Datei-Objekt erwartet wird. Hier zB.:
Code: Alles auswählen
def write_some_state_to_file(state, out_file):
data = str(state)
out_file.open()
out_file.write(data)
out_file.close()
Wenn ich jetzt diese Klasse schreiben würde:
Code: Alles auswählen
class PseudoFileThatWritesToScreen:
def open(self, mode):
pass
def close(self):
pass
def write(self, data):
print(data)
dann könnte ich folgendes machen:
Code: Alles auswählen
some_number = 123
screen_with_file_interface = PseudoFileThatWritesToScreen()
write_some_state_to_file(some_number, screen_with_file_interface)
Diese Austauschbarkeit der Implementiertung unter Beibehaltung der öffentlichen Schnittstelle nennt man Polymorphismus. Sie ist das zentrale Konzept der Objektorientierten Programmierung.
Was sollte man alles zur Klasse machen? Üblicherweise das, was man mittels Nomina referenziert. Also zB. Kunde, Artikel, Bestellung, Warenkorb, usw. Der Code könnte vielleicht so aussehen:
Code: Alles auswählen
from datetime import datetime
from uuid import uuid4
class Order:
def __init__(self, customer_id, line_items):
self._id = uuid4()
self._customer_id = customer_id
self._line_items = tuple(line_items)
self._date_placed = datetime.now()
class Customer:
def __init__(self, id, name, address):
self._id = id
self._name = name
self._address = address
self._cart = []
self._orders = []
def add_line_item_to_cart(self, article_id, amount):
if amount < 1:
raise Exception('cannot add zero or negative amount to cart.')
self._cart.append([article_id, amount])
def place_order(self):
self._orders.append(Order(self._id, self._cart))
self._cart = []
def get_all_ordered_articles(self, db):
article_ids = set()
for order in self._orders:
for article_id, amount in order.line_items:
article_ids.add(article_id)
articles = ... # <-- get articles by id from db
return articles
class Article:
...
...
customer = Customer(uuid4(), 'Joe Shmoe', 'Somewhere Street 123 in Sometown')
article = Article(...)
customer.add_line_item_to_cart(article.id, 3)
customer.place_order()
Aller Code ist ungetestet und dient nur zur Illustration.
In specifications, Murphy's Law supersedes Ohm's.