{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Software Entwicklung \n",
    "\n",
    "## Kapitel 8: Objektorientierung\n",
    "\n",
    "### 8.4 Eigene Klassen\n",
    "\n",
    "Bislang war die einzige Möglichkeit der Strukturierung\n",
    "größerer Programme die Zerlegung in Funktionen und Module.\n",
    "Durch die Programmierung *eigener Klassen* kommt ein\n",
    "weiteres mächtiges Strukturierungswerkzeug hinzu.\n",
    "\n",
    "Eingeleitet wird die Definiton einer Klasse durch\n",
    "das Schlüsselwort <code>class</code>, gefolgt vom Klassennamen,\n",
    "einem Klammernpaar (dazu später mehr) und einem Doppelpunkt.\n",
    "Danach folgen eingerückt in gewohnter Weise die Definitionen,\n",
    "die den Bauplan ausmachen. Klassennamen beginnen nach Konvention mit\n",
    "einem Großbuchstaben (Ausnahme: Klassen der Standardbibliothek sind\n",
    "oft kleingeschrieben, um sie wie \"normale\" Datentypen aussehen zu lassen)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Hinweis: das Schlüsselwort <code>pass</code> ist die leere Anweisung\n",
    "(\"Tue nichts\") und kann verwendet werden, wenn die Python-Syntax\n",
    "eine Anweisung erfordert, man aber (noch) keine Anweisung angeben will.\n",
    "\n",
    "Somit ist die oben skizzierte Klasse eine minimale Klassendefinition. Sie\n",
    "kann aber bereits benutzt werden, um Instanzen zu erzeugen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "s1 = Student()\n",
    "s2 = Student()\n",
    "print(type(s1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Innerhalb der Klassendefinition können nun Methoden definiert werden.\n",
    "Die Definition gleicht der Definition von Funktionen, jedoch\n",
    "muss in der Parameterliste immer als erstes der Parameter\n",
    "<code>self</code> aufgeführt werden, der dann im Methodenrumpf\n",
    "das eigene Objekt repräsentiert. Es gibt also keine\n",
    "Methodendefinitionen mit leerer Parameterliste!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "    def print_my_type(self):\n",
    "        print(type(self))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Beim Aufruf der Methode wird der erste Parameter weggelassen. Er\n",
    "wird von Python selbständig gesetzt, indem das Objekt eingesetzt wird,\n",
    "das links vom Punkt beim Aufruf über die Dot-Notation steht."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "s = Student()\n",
    "s.print_my_type()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alternativ ist auch eine Langform möglich, die aber kaum genutzt wird."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "s = Student()\n",
    "Student.print_my_type(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Der Bezeichner <code>self</code> kann auch genutzt werden,\n",
    "um innerhalb einer Methode auf Attribute der Instanz zuzugriffen\n",
    "oder sie erstmalig zu definieren."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def set_name(self, name):\n",
    "        self.name = name\n",
    "\n",
    "    def get_name(self):\n",
    "        return self.name\n",
    "\n",
    "s1 = Student()\n",
    "s1.set_name(\"Hans\")\n",
    "s2 = Student()\n",
    "s2.set_name(\"Hilde\")\n",
    "print(s1.get_name())\n",
    "print(s2.get_name())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Im Beispiel wird auf das Attribut <code>name</code> mit Hilfe\n",
    "einer Methode <code>get_name</code> zugegriffen. Es ist guter\n",
    "Stil und absolut üblich, lediglich die Methoden eines Objekts\n",
    "direkt auf die Attribute zugreifen zu lassen, und\n",
    "Zugriffe \"von außen\" über sog. *Getter-Funktionen*\n",
    "abzuwickeln.\n",
    "\n",
    "Grundsätzlich möglich wäre es aber:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "print(s2.name)\n",
    "s1.name = \"Hubert\"\n",
    "print(s1.name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 8.5 Zugriffsschutz\n",
    "\n",
    "Attribute (und Methoden) sind also in Python *öffentlich*,\n",
    "d.h. von außen zugreifbar und nutzbar. Um das\n",
    "unkontrollierte Ändern von Attributen zu unterbinden,\n",
    "haben sich aber einige Konventionen durchgesetzt:\n",
    "\n",
    "Attribute, die mit einem einfachen Unterstrich <code>\\_</code>\n",
    "beginnen, sind vom Entwickler als geschützt gekennzeichnet\n",
    "und sollten nicht direkt genutzt werden. Möglich ist es aber weiterhin!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def set_name(self, name):\n",
    "        self._name = name\n",
    "\n",
    "    def get_name(self):\n",
    "        return self._name\n",
    "\n",
    "s = Student()\n",
    "s._name = \"Horst\"\n",
    "print(s.get_name())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Beginnt der Attributname mit zwei Unterstrichen (bitte nicht zusätzlich mit\n",
    "zwei Unterstrichen beenden, s.o.), so ist der Zugriff auf das Attribut nur\n",
    "noch aus Methoden der Klasse möglich.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def set_name(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def get_name(self):\n",
    "        return self.__name\n",
    "\n",
    "s = Student()\n",
    "s.set_name(\"Hans\")\n",
    "print(s.get_name())\n",
    "s.__name = \"Horst\"\n",
    "print(s.get_name())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 8.6 Magic Methods\n",
    "\n",
    "Einige besondere Methodennamen, die sog. *Magic Methods* sind reserviert.\n",
    "Die sind alle *dunder methods*, d.h. ihr Name\n",
    "beginnt und endet mit zwei Unterstichen. Sie werden bei\n",
    "bestimmten Ereignissen vom Python-Laufzeitsystem ausgeführt, d.h.\n",
    "als Entwickler ruft man sie i.d.R. nicht direkt auf.\n",
    "\n",
    "Es ist nicht zwingend erforderlich, diese Methoden zu definieren\n",
    "und zu implementieren; mit ihrer Implementierung kann aber\n",
    "das Verhalten der Klasse beeinflusst werden.\n",
    "\n",
    "#### 8.6.1 Konstruktor\n",
    "\n",
    "Es kann je Klasse ein Konstruktor implementiert werden. Das ist\n",
    "eine Methode, die unmittelbar beim Generieren einer neuen\n",
    "Instanz aufgerufen wird. Der Konstruktor hat den Methodennamen\n",
    "<code>\\_\\_init\\_\\_</code>."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def __init__(self):\n",
    "        print(\"Im Konstruktor\")\n",
    "\n",
    "s = Student()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Häufig wird der Konstruktor genutzt, um die Attribute der neuen\n",
    "Instanz vorzubelegen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def __init__(self):\n",
    "        self.__name = None\n",
    "\n",
    "    def set_name(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def get_name(self):\n",
    "        return \"N.N.\" if self.__name is None else self.__name\n",
    "\n",
    "s = Student()\n",
    "print(s.get_name())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Der Konstruktor kann auch Parameter besitzen, die dann bei der\n",
    "Neuanlage eines Objekts versorgt werden müssen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def __init__(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def set_name(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def get_name(self):\n",
    "        return \"N.N.\" if self.__name is None else self.__name\n",
    "\n",
    "s = Student(\"Hilde\")\n",
    "print(s.get_name())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Attribute können auch direkt in der Klasse vorbelegt werden. Bei Erstellung des Objekts exisitiert das Attribut mit dem entsprechenden Wert bereits. Die bietet sich z.B. für konstante Werte einer Klasse an."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student():\n",
    "    __DEFAULT_NAME = 'N.N.'\n",
    "\n",
    "    def __init__(self, name=None):\n",
    "        self.__name = name\n",
    "\n",
    "    def set_name(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def get_name(self):\n",
    "        return self.__DEFAULT_NAME if self.__name is None else self.__name\n",
    "    \n",
    "s = Student()\n",
    "print(s.get_name())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "#### 8.6.2 Umwandlung in einen String\n",
    "\n",
    "Viele Built-In-Funktions rufen eine *magic Method* auf, so\n",
    "auch <code>str</code>, die ihren Parameter bekanntlich in einen\n",
    "String umwandelt. In diesem Fall wird <code>\\_\\_str\\_\\_</code> aufgerufen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def __init__(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def __str__(self):\n",
    "        return \"<\" + self.__name + \">\"\n",
    "\n",
    "s = Student(\"Horst\")\n",
    "print(str(s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 8.7 Identität\n",
    "\n",
    "Wie bei jedem neuen Datentyp steht die Frage im Raum, ob es sich\n",
    "um einen veränderlichen oder unveränderlichen Datentyp handelt. In Python\n",
    "bedeutet unveränderlich, dass bei Änderungen ein neues Objekt\n",
    "mit einer veränderten Identität erzeugt wird."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def __init__(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def set_name(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def get_name(self):\n",
    "        return \"N.N.\" if self.__name is None else self.__name\n",
    "\n",
    "s = Student(\"Hilde\")\n",
    "print(id(s))\n",
    "s.set_name(\"Herta\")\n",
    "print(id(s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Da die Identität des Objekts gleich bleibt, handelt es sich bei\n",
    "Python-Objekten um einen *veränderlichen* Datentyp (wie Listen und Mengen).\n",
    "Allerdings können Python-Objekte Schlüssel für Dictionaries\n",
    "sein, da in diesem Fall die Identität als Schlüssel verwendet wird.\n",
    "\n",
    "Obwohl Objekte veränderlich sind, entspricht trotzdem (zunächst)\n",
    "die Gleichheit der Identität bei Objekten.\n",
    "Anders als bei Listen oder Mengen findet also kein elementweiser\n",
    "Vergleich auf Basis der Attribute statt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "s1 = Student(\"Hilde\")\n",
    "s2 = Student(\"Hilde\")\n",
    "\n",
    "if id(s1) == id(s2):\n",
    "    print(\"Identisch\")\n",
    "\n",
    "if s1 == s2:\n",
    "    print(\"Gleich\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "Das Verhalten bei der Betrachtung der Gleichheit kann jedoch\n",
    "durch Implementierung der *magic Method*  <code>\\_\\_eq\\_\\_</code>\n",
    "verändert werden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "class Student():\n",
    "\n",
    "    def __init__(self, name):\n",
    "        self.__name = name\n",
    "\n",
    "    def __eq__(self, other):\n",
    "        return self.__name == other.__name\n",
    "\n",
    "s1 = Student(\"Hilde\")\n",
    "s2 = Student(\"Hilde\")\n",
    "\n",
    "if id(s1) == id(s2):\n",
    "    print(\"Identisch\")\n",
    "\n",
    "if s1 == s2:\n",
    "    print(\"Gleich\")\n",
    "\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Slideshow",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
