|
1908 | 1908 | " print_dict(employee.to_dict())"
|
1909 | 1909 | ]
|
1910 | 1910 | },
|
| 1911 | + { |
| 1912 | + "cell_type": "markdown", |
| 1913 | + "id": "9f680e25", |
| 1914 | + "metadata": {}, |
| 1915 | + "source": [ |
| 1916 | + "## Inheritance Tax\n", |
| 1917 | + "\n", |
| 1918 | + "- \"You wanted a banana but what you got was a gorilla holding the banana and the entire jungle\" - Joe Armstrong\n", |
| 1919 | + "- inheritance is a powerful tool but it can be misused\n", |
| 1920 | + "- alternatives may be better to avoid the inheritance tax:\n", |
| 1921 | + " - interfaces and protocols\n", |
| 1922 | + " - delegation\n", |
| 1923 | + " - mixins\n", |
| 1924 | + "- interfaces and protocols give us polymorphism without inheritance\n", |
| 1925 | + "- delegation is a way to use composition instead of inheritance\n", |
| 1926 | + "- delegate to services - Has-A relationship Trumps Is-A relationship\n", |
| 1927 | + "- use mixins to share code and functionality\n" |
| 1928 | + ] |
| 1929 | + }, |
1911 | 1930 | {
|
1912 | 1931 | "cell_type": "markdown",
|
1913 | 1932 | "id": "80057b78",
|
1914 | 1933 | "metadata": {},
|
1915 | 1934 | "source": [
|
1916 | 1935 | "## SOLID principle\n",
|
| 1936 | + "\n", |
1917 | 1937 | "- a popular software design principle using OOP\n",
|
1918 |
| - "- **inheritance** must follow L - Liskov Substitution principle\n", |
1919 | 1938 | "\n",
|
1920 |
| - "### S: Single Responsibility Principle\n", |
| 1939 | + "### S: Single Responsibility Principle (SRP)\n", |
| 1940 | + "\n", |
1921 | 1941 | "- a class should have one responsibility\n",
|
1922 |
| - "- one reason to change when the requirements change\n", |
| 1942 | + "- \"one and only reason\" to change when the requirements change\n", |
| 1943 | + "- a class should have a single, well-defined purpose; should be responsible for one aspect of the system's functionality\n", |
| 1944 | + "\n", |
| 1945 | + "#### How to Achieve the Single Responsibility Principle:\n", |
| 1946 | + "\n", |
| 1947 | + "- **Identify Responsibilities**:\n", |
| 1948 | + " - carefully analyze the responsibilities of each class in your design\n", |
| 1949 | + "\n", |
| 1950 | + "- **Separate Concerns**: \n", |
| 1951 | + " - if a class has multiple responsibilities, break it down into smaller classes, each with a single responsibility\n", |
| 1952 | + " \n", |
| 1953 | + "- **Use Cohesion**:\n", |
| 1954 | + " - aim for high cohesion within classes, meaning that all the elements within a class should be related to its single responsibility\n", |
| 1955 | + " \n", |
| 1956 | + "- **Avoid God Objects**:\n", |
| 1957 | + " - \"God objects\" are classes that try to do everything\n", |
| 1958 | + " \n", |
| 1959 | + "- **Refactor**: \n", |
| 1960 | + " - if you find that a class is violating the SRP, refactor it to separate its responsibilities\n", |
| 1961 | + " \n", |
| 1962 | + "#### What does \"reason to change\" mean?\n", |
| 1963 | + "\n", |
| 1964 | + "- A \"reason to change\" refers to a specific actor or requirement that could cause the class to be modified; e.g.:\n", |
| 1965 | + "\n", |
| 1966 | + " - A class that handles both data persistence and user interface logic has two reasons to change: changes in the database schema and changes in the UI design.\n", |
| 1967 | + " - A class that both calculates employee salary and generates reports has two reasons to change: changes in salary calculation rules and changes in report formatting.\n", |
| 1968 | + "\n" |
| 1969 | + ] |
| 1970 | + }, |
| 1971 | + { |
| 1972 | + "cell_type": "code", |
| 1973 | + "execution_count": null, |
| 1974 | + "id": "5409782d", |
| 1975 | + "metadata": {}, |
| 1976 | + "outputs": [], |
| 1977 | + "source": [ |
| 1978 | + "# e.g. design violating SRP\n", |
| 1979 | + "class Employee:\n", |
| 1980 | + " def __init__(self, employee_id, name, salary):\n", |
| 1981 | + " self.employee_id = employee_id\n", |
| 1982 | + " self.name = name\n", |
| 1983 | + " self.salary = salary\n", |
| 1984 | + "\n", |
| 1985 | + " def calculate_pay(self):\n", |
| 1986 | + " # Calculate employee pay\n", |
| 1987 | + " pass\n", |
| 1988 | + "\n", |
| 1989 | + " def generate_report(self):\n", |
| 1990 | + " # Generate employee report\n", |
| 1991 | + " pass\n", |
| 1992 | + "\n", |
| 1993 | + " def save_to_database(self):\n", |
| 1994 | + " # Save employee data to database\n", |
| 1995 | + " pass" |
| 1996 | + ] |
| 1997 | + }, |
| 1998 | + { |
| 1999 | + "cell_type": "code", |
| 2000 | + "execution_count": null, |
| 2001 | + "id": "f43f76ad", |
| 2002 | + "metadata": {}, |
| 2003 | + "outputs": [], |
| 2004 | + "source": [ |
| 2005 | + "# to adhere to SRP refactor the God class into three separate classes\n", |
| 2006 | + "class Employee:\n", |
| 2007 | + " def __init__(self, employee_id, name, salary):\n", |
| 2008 | + " self.employee_id = employee_id\n", |
| 2009 | + " self.name = name\n", |
| 2010 | + " self.salary = salary\n", |
| 2011 | + "\n", |
| 2012 | + "class PayCalculator:\n", |
| 2013 | + " def calculate_pay(self, employee):\n", |
| 2014 | + " # Calculate employee pay\n", |
| 2015 | + " pass\n", |
1923 | 2016 | "\n",
|
| 2017 | + "class ReportGenerator:\n", |
| 2018 | + " def generate_report(self, employee):\n", |
| 2019 | + " # Generate employee report\n", |
| 2020 | + " pass\n", |
| 2021 | + "\n", |
| 2022 | + "class EmployeeRepository:\n", |
| 2023 | + " def save_to_database(self, employee):\n", |
| 2024 | + " # Save employee data to database\n", |
| 2025 | + " pass" |
| 2026 | + ] |
| 2027 | + }, |
| 2028 | + { |
| 2029 | + "cell_type": "markdown", |
| 2030 | + "id": "259b858a", |
| 2031 | + "metadata": {}, |
| 2032 | + "source": [ |
1924 | 2033 | "### O: Open/Closed\n",
|
1925 |
| - "- a class should be open to extension but closed to modification\n", |
1926 | 2034 | "\n",
|
| 2035 | + "- \"Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.\"\n", |
| 2036 | + "- Open for Extension: \n", |
| 2037 | + " - you should be able to add new functionality to your software without changing the existing code\n", |
| 2038 | + "- Closed for Modification\n", |
| 2039 | + " - once a piece of code is written and tested, you shouldn't need to modify its source code to accommodate new requirements\n", |
| 2040 | + " \n", |
| 2041 | + "#### Why is this important?\n", |
| 2042 | + "\n", |
| 2043 | + "- reduced risk of introducing bugs\n", |
| 2044 | + " - modifying existing code can introduce unintended side effects and break working functionality\n", |
| 2045 | + " - by extending instead of modifying, you minimize this risk\n", |
| 2046 | + " \n", |
| 2047 | + "- increased maintainability\n", |
| 2048 | + " - code that adheres to the OCP is easier to maintain and adapt to changing requirements\n", |
| 2049 | + "\n", |
| 2050 | + "- improved reusability\n", |
| 2051 | + " - well-designed, extensible code can be reused in different parts of your application or in other projects\n", |
| 2052 | + " \n", |
| 2053 | + "- enhanced stability\n", |
| 2054 | + " - stable, unmodified code provides a reliable foundation for building new features\n", |
| 2055 | + " \n", |
| 2056 | + "#### How to Achieve the Open/Closed Principle:\n", |
| 2057 | + "\n", |
| 2058 | + "- common techniques for achieving the OCP include:\n", |
| 2059 | + "\n", |
| 2060 | + "- **Abstraction**: \n", |
| 2061 | + " - using abstract classes or interfaces to define a contract that concrete classes must adhere to\n", |
| 2062 | + " \n", |
| 2063 | + "- **Inheritance**: \n", |
| 2064 | + " - creating new classes that inherit from existing classes and override or extend their behavior\n", |
| 2065 | + "\n", |
| 2066 | + "- **Polymorphism**: \n", |
| 2067 | + " - writing code that can work with objects of different types through a common interface\n", |
| 2068 | + " \n", |
| 2069 | + "- **Composition**: \n", |
| 2070 | + " - building complex objects by combining simpler objects, rather than inheriting from them\n", |
| 2071 | + " \n", |
| 2072 | + "- **Design Patterns**: \n", |
| 2073 | + " - using design patterns like the Strategy, Template Method, and Observer patterns, which are specifically designed to promote extensibility\n", |
| 2074 | + " \n", |
1927 | 2075 | "### L: Liskov Substitution\n",
|
| 2076 | + "\n", |
| 2077 | + "- **inheritance** must follow L - Liskov Substitution principle\n", |
1928 | 2078 | "- named after Barbara Liskov creator of CLU programming language\n",
|
1929 | 2079 | "- any subclass can be substituted for its superclass\n",
|
1930 | 2080 | " - any code that uses superclass can be replaced with it's subclass without breaking any code!\n",
|
|
1940 | 2090 | "- if two classes depend on each other, use a mixin class to reuse the dependence"
|
1941 | 2091 | ]
|
1942 | 2092 | },
|
1943 |
| - { |
1944 |
| - "cell_type": "markdown", |
1945 |
| - "id": "9f680e25", |
1946 |
| - "metadata": {}, |
1947 |
| - "source": [ |
1948 |
| - "## Inheritance Tax\n", |
1949 |
| - "\n", |
1950 |
| - "- \"You wanted a banana but what you got was a gorilla holding the banana and the entire jungle\" - Joe Armstrong\n", |
1951 |
| - "- inheritance is a powerful tool but it can be misused\n", |
1952 |
| - "- alternatives may be better to avoid the inheritance tax:\n", |
1953 |
| - " - interfaces and protocols\n", |
1954 |
| - " - delegation\n", |
1955 |
| - " - mixins\n", |
1956 |
| - "- interfaces and protocols give us polymorphism without inheritance\n", |
1957 |
| - "- delegation is a way to use composition instead of inheritance\n", |
1958 |
| - "- delegate to services - Has-A relationship Trumps Is-A relationship\n", |
1959 |
| - "- use mixins to share code and functionality\n" |
1960 |
| - ] |
1961 |
| - }, |
1962 | 2093 | {
|
1963 | 2094 | "cell_type": "markdown",
|
1964 | 2095 | "id": "86ab0315",
|
|
0 commit comments