diff --git a/topics/CMakeLists.txt b/topics/CMakeLists.txt index 76d756f..396d885 100644 --- a/topics/CMakeLists.txt +++ b/topics/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(build_systems) add_subdirectory(docker) add_subdirectory(git) add_subdirectory(linux) +add_subdirectory(sw_concepts) add_subdirectory(testing) add_subdirectory(tools_and_varia) diff --git a/topics/sw_concepts/CMakeLists.txt b/topics/sw_concepts/CMakeLists.txt new file mode 100644 index 0000000..a8aa080 --- /dev/null +++ b/topics/sw_concepts/CMakeLists.txt @@ -0,0 +1,7 @@ +include(js_document) + +js_slides(sw_concept_slides sw_concept_slides.md) +js_slides(sw_concept_code_examples sw_concept_code_examples.md) + +file(GLOB_RECURSE code RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "code/*") +js_add_to_global_archive_file_list(${code}) diff --git a/topics/sw_concepts/code/abstractions.py b/topics/sw_concepts/code/abstractions.py new file mode 100644 index 0000000..ec590ca --- /dev/null +++ b/topics/sw_concepts/code/abstractions.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +import errno +import math +import os +import sys +import time +from optparse import OptionParser + +# Censored hardware prototype2 and series (tsl2550) value range: 0 - 4015, daylight: 544 +# Censored hardware v3 (opt3001) value range: 0 - 36157, daylight: 290 +INPUT_MIN = 0 +INPUT_MAX_TSL2550 = 544 +INPUT_MAX_OPT3001 = 290 +INPUT_THRESHOLD = 11 + +# Censored hardware prototype2 (Intel backlight) value range: 0 - 937 +# Censored hardware series Device (Intel backlight) value range: 0 - 7500 +OUTPUT_MIN_FACTOR = 0.2 +OUTPUT_CHANGE_MAX_FACTOR = 0.005 + +sensor_value_lux_approx_map = [ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 49, 53, 57, 61, 65, 69, 73, 77, + 81, 85, 89, 93, 97, 101, 105, 109, + 115, 123, 131, 139, 147, 155, 163, 171, + 179, 187, 195, 203, 211, 219, 227, 235, + 247, 263, 279, 295, 311, 327, 343, 359, + 375, 391, 407, 423, 439, 455, 471, 487, + 511, 543, 575, 607, 639, 671, 703, 735, + 767, 799, 831, 863, 895, 927, 959, 991, + 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, + 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, + 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, + 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015 +] + + +def main(): + input_max = INPUT_MAX_TSL2550 + output_last_value = None + + parser = OptionParser() + _, args = parser.parse_args() + + if len(args) != 3: + raise Exception("wrong number of arguments") + + path = args[0] + input_path = args[1] + output_path = args[2] + opt3001 = "in_illuminance_input" in os.readlink(input_path) + + input_last_value = INPUT_MIN - INPUT_THRESHOLD + with open(path) as f: + output_max = int(f.readline().strip()) + output_min = int(math.ceil(output_max * OUTPUT_MIN_FACTOR)) + output_change_max = int(math.ceil(output_max * OUTPUT_CHANGE_MAX_FACTOR)) + + while True: + try: + if opt3001: + try: + with open(input_path) as f1: + input_value = float(f1.readline().strip()) + except IOError: + # This driver generates a read error if very little light is present + input_value = INPUT_MIN + else: + with open(input_path) as f: + input_value = int(f.readline().strip()) + if 0 <= input_value < len(sensor_value_lux_approx_map): + input_value = sensor_value_lux_approx_map[input_value] + else: + input_value = input_last_value + + input_value = min(input_value, input_max) + + # Ignore small input value changes + if abs(input_value - input_last_value) < INPUT_THRESHOLD: + input_value = input_last_value + + a = (input_value - INPUT_MIN) / float(input_max - INPUT_MIN) + value1 = int(a * float(output_max - output_min) + output_min) + output_value = min(value1, output_max) + + if output_last_value is None: + output_value = output_value + elif output_value >= output_last_value: + output_value = min(output_value, output_last_value + output_change_max) + else: + output_value = max(output_value, output_last_value - output_change_max) + dimmed_value = output_value + + if output_value != output_last_value: + print(f"input: %4i (%4.1f%%), output: %4i (%4.1f%%), dimmed: %4i (%4.1f%%)", + input_value, 100 * (input_value - INPUT_MIN) / float(input_max - INPUT_MIN), + output_value, 100 * (output_value - output_min) / float(output_max - output_min), + dimmed_value, 100 * (dimmed_value - output_min) / float(output_max - output_min)) + sys.stdout.flush() + + with open(output_path, "w") as f: + output_last_value = output_value + f.write(str(output_value)) + + input_last_value = input_value + except IOError as e: + # Ignore EGAIN errors which may happen if the I2C bus is busy + if e.errno != errno.EAGAIN: + raise e + + time.sleep(0.01) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/topics/sw_concepts/images/button_factory/footer_c.png b/topics/sw_concepts/images/button_factory/footer_c.png new file mode 100644 index 0000000..2f4cae4 Binary files /dev/null and b/topics/sw_concepts/images/button_factory/footer_c.png differ diff --git a/topics/sw_concepts/images/button_factory/footer_m.png b/topics/sw_concepts/images/button_factory/footer_m.png new file mode 100644 index 0000000..d4fca5b Binary files /dev/null and b/topics/sw_concepts/images/button_factory/footer_m.png differ diff --git a/topics/sw_concepts/images/dependency-inversion/no-dependency-inversion.png b/topics/sw_concepts/images/dependency-inversion/no-dependency-inversion.png new file mode 100644 index 0000000..20686d1 Binary files /dev/null and b/topics/sw_concepts/images/dependency-inversion/no-dependency-inversion.png differ diff --git a/topics/sw_concepts/images/dependency-inversion/no-dependency-inversion.puml b/topics/sw_concepts/images/dependency-inversion/no-dependency-inversion.puml new file mode 100644 index 0000000..aa24627 --- /dev/null +++ b/topics/sw_concepts/images/dependency-inversion/no-dependency-inversion.puml @@ -0,0 +1,13 @@ +@startuml + +package domain { + class TransactionObserver +} + +package persistence { + class DBTransactionRepository +} + +TransactionObserver --> DBTransactionRepository + +@enduml diff --git a/topics/sw_concepts/images/dependency-inversion/with-dependency-inversion.png b/topics/sw_concepts/images/dependency-inversion/with-dependency-inversion.png new file mode 100644 index 0000000..e10bcd9 Binary files /dev/null and b/topics/sw_concepts/images/dependency-inversion/with-dependency-inversion.png differ diff --git a/topics/sw_concepts/images/dependency-inversion/with-dependency-inversion.puml b/topics/sw_concepts/images/dependency-inversion/with-dependency-inversion.puml new file mode 100644 index 0000000..286d683 --- /dev/null +++ b/topics/sw_concepts/images/dependency-inversion/with-dependency-inversion.puml @@ -0,0 +1,15 @@ +@startuml + +package domain { + class TransactionObserver + + interface TransactionRepository +} + +package persistence { + class DBTransactionRepository implements domain.TransactionRepository +} + +TransactionObserver --> TransactionRepository + +@enduml diff --git a/topics/sw_concepts/images/interface-segregation/java-interface-segregation.png b/topics/sw_concepts/images/interface-segregation/java-interface-segregation.png new file mode 100644 index 0000000..1d6800c Binary files /dev/null and b/topics/sw_concepts/images/interface-segregation/java-interface-segregation.png differ diff --git a/topics/sw_concepts/images/interface-segregation/java-interface-segregation.puml b/topics/sw_concepts/images/interface-segregation/java-interface-segregation.puml new file mode 100644 index 0000000..2932030 --- /dev/null +++ b/topics/sw_concepts/images/interface-segregation/java-interface-segregation.puml @@ -0,0 +1,56 @@ +@startuml + +!theme plain +skinparam linetype ortho + +class ArrayList { +} +interface Cloneable << interface >> +interface Collection << interface >> { + + remove(Object): boolean + + clear(): void + + removeIf(Predicate): boolean + + isEmpty(): boolean + + addAll(Collection): boolean + + size(): int + + hashCode(): int + + contains(Object): boolean + + add(E): boolean +} +class LinkedHashSet { +} +interface List << interface >> { + + sort(Comparator): void + + isEmpty(): boolean + + contains(Object): boolean + + add(E): boolean + + set(int, E): E + + remove(Object): boolean + + lastIndexOf(Object): int + + size(): int + + remove(int): E + + get(int): E + + add(int, E): void + + clear(): void + + indexOf(Object): int + + containsAll(Collection): boolean +} +interface Serializable << interface >> +interface Set << interface >> { + + contains(Object): boolean + + add(E): boolean + + remove(Object): boolean + + isEmpty(): boolean + + clear(): void + + size(): int +} + +ArrayList -[#008200,dashed]--^ Cloneable +ArrayList -[#008200,dashed]--^ List +ArrayList -[#008200,dashed]--^ Serializable +LinkedHashSet -[#008200,dashed]--^ Cloneable +LinkedHashSet -[#008200,dashed]--^ Serializable +LinkedHashSet -[#008200,dashed]--^ Set +List -[#008200,plain]--^ Collection +Set -[#008200,plain]--^ Collection +@enduml diff --git a/topics/sw_concepts/sw_concept_code_examples.md b/topics/sw_concepts/sw_concept_code_examples.md new file mode 100644 index 0000000..df50fea --- /dev/null +++ b/topics/sw_concepts/sw_concept_code_examples.md @@ -0,0 +1,408 @@ +<#include meta/slides.md> + +--- +title: "SW Konzepte Code Beispiele" +date: \today +--- + +Code Beispiele +------- + +Überlege für die folgenden Code Beispiele folgendes: + +1. Hat das Beispiel etwas mit DRY, KISS, YAGNI oder NIH zu tun? +2. Hat es mit den SOLID Principles zu tun? +3. Welche Design Pattern siehst du? +4. Was könnte sonst noch verbessert werden? + +OrganisationSupplier +------- + +```python +class OrganisationSupplier: + def get_current_organisation(self): + pass + +class ConfigurationOrganisationSupplier(OrganisationSupplier): + def __init__(self, device_location_dao, konfiguration_dao, default_organisation): + self.konfiguration_dao = konfiguration_dao + self.default_organisation = default_organisation + + def get_current_organisation(self): + return self.konfiguration_dao.get_konfigurationKonfigurationDAO.ORGANISATION_ID, self.default_organisation) + +class DeviceLocationOrganisationSupplier(OrganisationSupplier): + def __init__(self, konfiguration_dao, device_location_dao): + self.konfiguration_dao = konfiguration_dao + self.device_location_dao = device_location_dao + + def get_current_organisation(self): + return Optional.ofNullable(self.konfiguration_dao.get_konfiguration(KonfigurationDAO.STANDORT_KENNUNG, None)) \ + .map(int) \ + .map(self.device_location_dao.get_device_location_by_id) \ + .map(lambda device_location_entity: device_location_entity.get_organisation()) +``` + +OrganisationSupplier Auswertung +------- + +1. DRY...: Property Keys werden geteilt (e.g. STANDORT_KENNUNG) +2. SOLID: nicht speziell +3. Pattern: Strategy +4. Verbesserung: - + +AtmelObservableFactory +------- + +```python +class AtmelObservableFactory: + @staticmethod + def create_power_state(host, port, scheduler): + return AtmelObservableFactory.create_power_state_with_interval(host, port, INTERVAL, scheduler) + + @staticmethod + def create_power_state_with_interval(host, port, interval, scheduler): + simple_request = SimpleRequest("inputGetIgnitionState") + ignition_state_request_executor = AtmelRequestExecutor(host, port, simple_request, IgnitionStateResult) + return PollingObservable.create(scheduler, lambda: ..., interval, scheduler) + + @staticmethod + def create_system_state(host, port, scheduler): + return AtmelObservableFactory.create_system_state_with_interval(host, port, INTERVAL, scheduler) + + @staticmethod + def create_system_state_with_interval(host, port, interval, scheduler): + simple_request = SimpleRequest("registerObject", ["InfovisionSystemState"]) + return RetryUntilSuccess.create(AtmelRequestExecutor(host, port, simple_request, SystemStateResult), lambda: interval, scheduler) +``` + +AtmelObservableFactory Auswertung +------- + +1. DRY...: YAGNI: muss das Interval konfigurierbar sein? +2. SOLID: Single Responsibility: muss beides hier erstellt werden? +3. Pattern: AbstractFactory, Factory method +4. Verbesserung: - + +BrightnessParserTest +------- + +```python +def test_parses_brightness_correctly(self): + windows_brightness_output = """ + Power Setting GUID: 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e (Turn off display after) + Minimum Possible Setting: 0x00000000 + Maximum Possible Setting: 0xffffffff + Possible Settings increment: 0x00000001 + Current AC Power Setting Index: 0x00000000 + Current DC Power Setting Index: 0x00003840 + + Power Setting GUID: aded5e82-b909-4619-9949-f5d71dac0bcb (Display brightness) + Minimum Possible Setting: 0x00000000 + Maximum Possible Setting: 0x00000064 + Possible Settings increment: 0x00000001 + Possible Settings units: % + Current AC Power Setting Index: 0x0000005a + Current DC Power Setting Index: 0x0000005a +""" + self.assertEqual(parse(windows_brightness_output), "0000005a") +``` + +BrightnessParserTest Auswertung +------- + +1. DRY...: "0000005a" könnte geteilt und benamst werden +2. SOLID: - +3. Pattern: - +4. Verbesserung: - + +DoorState +------- + +```java +private synchronized void setDoorState(DoorState newDoorState, + DoorComponent doorComponent) { + doorContext.setDoorState(newDoorState, doorComponent); + if (doorContext.isInconsistent()) { + observers.forEach(DoorObserver::inconsistentSensors); + } else if (DoorState.OPEN.equals(newDoorState)) { + observers.forEach(DoorObserver::switchOpened); + } else if (DoorState.CLOSED.equals(newDoorState) && doorContext.isDoorCompletelyClosed()) { + observers.forEach(DoorObserver::doorClosed); + } + } +``` + +DoorState Auswertung +------- + +1. DRY...: - +2. SOLID: Open/Closed: Für alle neuen Events muss das if statement und das DoorObserver Interface geändert werden. +3. Pattern: Observer +4. Verbesserung: - + +TransactionRepository +------- + +```java +public interface TransactionRepository { + Transaction addTransactions(Collection transactions); + + void updateTransactions(Collection transactions); + + Optional findTransaction(UUID uuid); +} + +HibernateTransactionRepository implements TransactionRepository {} + +public class TransactionObserverTest { + private TransactionObserver transactionObserver; + private TransactionRepository transactionRepository; + @Before + public void givenTransactionObserver() { + transactionRepository = mock(TransactionRepository.class); + transactionObserver = new TransactionObserver(transactionRepository, createTerminalInfoExporter(), createLocationInfoExporter()); + } +``` + +TransactionRepository Auswertung +------- + +1. DRY...: KISS: 1 Mock kann auch einfach mit einer Klasse erreicht werden +2. SOLID: Dependency Inversion +3. Pattern: - +4. Verbesserung: - + +CreditCardAppIdMapperProvider +------- + +```java +public class CreditCardAppIdMapperProvider implements Provider { + private final CreditCardAppIdMapper creditCardAppIdMapper; + + @Inject + public CreditCardAppIdMapperProvider(CreditCardAppIdMappingConfiguration configuration, + CreditCardAppIdCategoryMappingParser creditCardAppIdCategoryMappingParser) { + if (configuration.isEnabled()) { + List mappingEntries = creditCardAppIdCategoryMappingParser.parse(new File(configuration.getMappingFilePath())); + Map map = new HashMap<>(); + mappingEntries.forEach(mappingEntry -> { + CreditCardAppIdMapper.CardAppIdCategory keyToAdd = new CreditCardAppIdMapper.CardAppIdCategory(mappingEntry.getAppIdCategory()); + map.put(keyToAdd, mappingEntry.getLabel()); + }); + creditCardAppIdMapper = new CreditCardAppIdMapper(map); + } else { + creditCardAppIdMapper = new CreditCardAppIdMapper(new HashMap<>()); + } + } + + @Override + public CreditCardAppIdMapper get() { + return creditCardAppIdMapper; + } +} +``` + +CreditCardAppIdMapperProvider Auswertung +------- + +1. DRY...: - +2. SOLID: - +3. Pattern: Factory, [Null Object](https://github.com/iluwatar/java-design-patterns/tree/master/null-object) +4. Verbesserung: Das parsen des Files (Input/Output oder I/O) im Konstruktor +kann das aufstarten Verzögern. Das könnte man wenn möglich asynchron machen. + +Board +------- + +```java +public class Board { + private final List boardObservers = new ArrayList<>(); + + public void executeMove(Move move) { + Command command = createCommand(move); + command.execute(); + boardObservers.forEach(boardObserver -> boardObserver.boardChanged(this)); + } + + public void registerObserver(BoardObserver boardObserver) { + boardObservers.add(boardObserver); + } +} +``` + +Board Auswertung +------- + +1. DRY...: - +2. SOLID: - +3. Pattern: Observer +4. Verbesserung: Keine Möglichkeit um Observer zu entfernen -> Memory Leak + +Board2 +------- + +```python +class SimpleMove(Command): + def execute(self): + self.store.remove_piece(self.start.get_key()) + self.store.add_piece(self.end.get_key(), self.end.get_value()) + + def undo(self): + self.store.add_piece(self.start.get_key(), self.start.get_value()) + self.store.remove_piece(self.end.get_key()) + +class Board: + def __init__(self): + self.executed_commands = [] + + def execute_move(self, move): + command = self.create_command(move) + command.execute() + self.executed_commands.append(command) + + def undo_last_turn(self): + if len(self.executed_commands) == 0: + raise NoPreviousMovesException() + + last_command = self.executed_commands[-1] + last_command.undo() + self.executed_commands.pop() +``` + +Board2 Auswertung +------- + +1. DRY...: - +2. SOLID: - +3. Pattern: Command +4. Verbesserung: - + +DoorEventHandler +------- + +```java +public class DoorEventHandler { + private static final Logger LOGGER = Logger.getLogger(DoorEventHandler.class); + + private final DoorContext doorContext; + + private final Set observers = new HashSet<>(); + + public DoorEventHandler(DoorContext doorContext) { + this.doorContext = doorContext; + } + + public void addObserver(DoorObserver observer) { + } + + @Handler + public void handle(DoorEvent event) { + } +} +``` + +DoorEventHandler Auswertung +------- + +1. DRY...: - +2. SOLID: - +3. Pattern: Observer +4. Verbesserung: + 1. Liste der Observer muss je nach dem Threadsafe sein + 2. Keine Möglichkeit Observer zu entfernen -> Memory Leak + +ButtonFactory +------- + +```python +class ButtonFactory: + def get_bwd_button(self, name, dim, text): + return BwdButton(name, dim, text) + + def get_circle_button(self, name, color, text): + return CircleButton(name, color, text) + + def get_fwd_button(self, name, dim, text): + return FwdButton(name, dim, text) + + def get_info_button(self, name, info_text): + return InfoButton(name, info_text) + +class ButtonFactoryC(ButtonFactory): + pass +``` + +ButtonFactory Auswertung +------- + +1. DRY...: - +2. SOLID: - +3. Pattern: AbstractFactory +4. Verbesserung: Inheritance hat in diesem Fall zu Problemen geführt. +da man nicht Voraussagen kann, wo welche Buttons gebraucht werden, +bietet sich hier Composition + Interface besser an. + +ReceiptFactory +------- + +```python +class ReceiptFactory: + def create_eft_receipt(self, receipt_lines): + pass + + def create_refund_receipt(self, kunden_session_nr, fehl_betrag, beleg_nr, mfk): + pass + +class Customer1ReceiptFactory(ReceiptFactory): + def __init__(self, printer_config): + self.printer_config = printer_config + + def create_eft_receipt(self, receipt_lines): + pass + + def create_refund_receipt(self, kunden_session_nr, fehl_betrag, beleg_nr, mfk): + print("No refund receipt implemented") + return None +``` + +ReceiptFactory Auswertung +------- + +1. DRY...: - +2. SOLID: create_refund_receipt verletzt Liskov's Substitution Principle +3. Pattern: AbstractFactory +4. Verbesserung: - + +OperatingPoint +----- + +```python +class OperatingPointNumberResolveStrategy: + """ + Returns the vending location (Verkaufspunkt) assigned to the operating point number (Betriebspunkt) + """ + def get_net_point(self, operating_point_number): + pass + +class DidokStrategy(OperatingPointNumberResolveStrategy): + def get_net_point(self, operating_point_number): + return self.assortment_provider.get_current_netpoint_by_didok_number( + operating_point_number + ) + +class SubstopNetpointIdStrategy(OperatingPointNumberResolveStrategy): + def get_net_point(self, operating_point_number): + return self.assortment_provider.get_current_netpoint_by_netpoint_id( + operating_point_number, + "SUBSTOP" + ) +``` + +OperatingPoint Auswertung +------- + +1. DRY...: KISS: Hätte auch ein IF statement gereicht? +2. SOLID: - +3. Pattern: Strategy +4. Verbesserung: - diff --git a/topics/sw_concepts/sw_concept_slides.md b/topics/sw_concepts/sw_concept_slides.md new file mode 100644 index 0000000..9f40439 --- /dev/null +++ b/topics/sw_concepts/sw_concept_slides.md @@ -0,0 +1,439 @@ +<#include meta/slides.md> + +--- +title: "SW Konzepte" +date: \today +--- + +Was ist hier falsch? +------- + +Code Beispiel: [abstractions.py](code/abstractions.py) + +abstractions.py Zusammenfassung +------- + +* Fast keine Strukturierung durch Methoden/Funktionen +* Code duplication + +Abstraktion: Nachteile +------- + +* erhöht Komplexität +* Änderung der Abstraktion haben Auswirkungen auf alle User +* Die Abstraktion am falschen Ort (Vertikal) +* Die Abstraktion versteckt wichtige Features + +Abstraktion am falschen Ort +------ + +```java +public void initialize() { + carouselDisposable.dispose(); + carouselDisposable = contactlessPMod.getCarouselProvider() + .addCarouselObserver( + imagePath -> JfxThreadingUtils.invokeOnFx(() -> setContactlessLogo(imagePath)) + ); +} + +private void setContactlessLogo(String imagePath) { + if (imagePath == null || getAcceptance() != ACCEPTED) { + final String imageFileName = getImageFileName() + final String imageUrl = IMAGES_PATH + imageFileName; + vibbekLogo.setImage(new Image(imageUrl)); + } else { + if (contactlessPMod.isCarouselEnabled()) { + vibbekLogo.setImage(new Image(imagePath)); + } else { + vibbekLogo.setImage(new Image(IMAGES_PATH + "static_carousel.png")); + } + } +} +``` + +Agenda +------- + +* Generelle Prinzipien (DRY, KISS...) +* Clean Code +* Design Patterns + +DRY +------- + +* **D**on't **R**epeat **Y**ourself +* Für **Verhalten** aber auch für **Information** +* Was wäre cool, wenn es sich bei allen Copy-and-paste Orten ändern würde +* Gibt dem Extrahierten auch einen Namen + +DRY Beispiel für Konstanten +------ + +```java +assertEquals(new DateTime(2013, 6, 5, 12, 54).toDate(), view.getServiceStartDate()); +assertEquals(new Long(4), view.getServiceSerialId()); +assertEquals(new Long(2), view.getServicePeriodSerialId()); +assertEquals("RGS_EMPTYING_TO_RETURNTRAY", view.getServiceType()); +``` + +KISS +------- + +* **K**eep **i**t **S**hort and **S**imple\ + (Keep it simple stupid) + +YAGNI +------- + +* **Y**ou **a**n't **g**onna **n**eed **i**t + +NIH +------- + +* **N**ot **i**nvented **h**ere +* Dinge selbst implementieren, die schon in der Sprache/in einer Library sind. + +NIH Beispiel 1 +------- + + + +Clean Code +------- + +* **S**ingle Responsibility +* **O**pen Closed Principle +* **L**iskov's Substition Principle +* **I**nterface Segregation +* **D**ependency Inversion + +Single Responsibility +------- + +* Eine Funktion, Klasse, Modul sollte nur eine "Responsibility" haben. +* Grenze ist nicht ganz klar +* Indikator: wenn bei der benennung ein "and" vorkommt, dann sollte man aufteilen. +* Andere Sichtweise: Eine Funktion, Klasse, Modul sollte nur einen Grund haben, sich zu ändern. + +Beispiel Single Responsibility verletzt +------ + +```java +public enum DirectionAndRazziaValue { + DIRECTION_1, + DIRECTION_2, + RAZZIA +} + +private static void updateCountersAndLogEjectedCoins(...) + +public SessionFactoryBuilder withUserAndPassword(String username, String password) { + this.username = username; + this.password = password; + return this; +} + +``` + +Beispiel Single Responsibility verletzt mit Kommentar +------ + +```java +// Werte in unabhängige Felder packen +public enum DirectionAndRazziaValue { + DIRECTION_1, + DIRECTION_2, + RAZZIA +} + +// 2 Concerns trennen +private static void updateCountersAndLogEjectedCoins(...) + +// in 2 methoden aufteilen: es ist ja schon ein Builder +public SessionFactoryBuilder withUserAndPassword(String username, String password) { + this.username = username; + this.password = password; + return this; +} + +``` + +Open Closed +------- + +* "Open for extension, closed for change" +* Neue Funktionalität -> nicht bestehenden code anpassen, sondern möglichst nur code hinzufügen + +Open Closed Beispiel +------- + +Regeln für Dame: + +* Eigenen Spielstein bewegen +* Ziel Feld muss leer sein +* Bauer darf sich 1 weit Feld bewegen, der König 2 +* Bauer darf nur vorwärts +* Bewegung muss diagonal sein +* Beim Springen muss gegnerische Figur dazwischen sein +* Falls man springen kann, muss man springen + + + +Open Closed Beispiel 2 +------- + +Wenn alle Regeln in separaten Klassen sind, macht das den Inhalt klarer. +Auch ist es jetzt einfacher, neue Regeln hinzuzufügen. + + +```python +class StartPieceValid: +class TargetFieldEmpty: +class MoveLength: +class MoveIsForwardIfNotKing: +class MoveIsDiagonal: +class OpponentPieceBetweenJump: +class NoOtherJumpMovePossible: +``` + +Open Closed Beispiel 3 +------- + +Und es ermöglicht composition: + +```python + moveValidators = [ + startPieceValid, + targetFieldEmpty, + moveLength, + moveIsForwardIfNotKing, + moveIsDiagonal, + opponentPieceBetweenJump, + noOtherMoveToJumpPossible + ] + winCondition = new WinCondition([ + startPieceValid, + targetFieldEmpty, + moveLength, + moveIsForwardIfNotKing, + moveIsDiagonal, + opponentPieceBetweenJump + ]) +``` + +Liskovs substitution principle +------- + +* Implementationen eines Interfaces müssen ausgetauscht werden können. +* Sie nehmen mindestens die gleichen Input werte an (dürfen auch mehr). +* Sie geben maximal die gleichen Rückgabewerte zurück. +* Werfen maximal die gleichen Exceptions (oder Subtypen der Exceptions). + +Beispiel Liskovs substitution principle Verletzung +------- + +```python +class Vehicle: + pass + +class Car(Vehicle): + pass + +class Bicycle(Vehicle): + pass + +class Garage: + def park(self, vehicle: Vehicle): + pass + +class BicycleGarage(Garage): + def park(self, vehicle: Vehicle): + if (not isinstance(vehicle, Bicycle)): + raise ValueError("BicycleGarage can only park bicycles") + +``` + +Interface Segregation +------- + +* Um die Kopplung zu reduzieren, kann man auch Interfaces aufteilen. +* Eine Klasse kann mehrere Interfaces implementieren +* Man will mit einer Klasse nicht benötigte Methoden nicht implementieren +* Man will nicht, dass jemand plötzlich eine Methode nutzt, die man gar nicht zur Verfügung stellen wollte. + +Interface Segregation Example +------ + +![Interface Segregation](images/interface-segregation/java-interface-segregation.png){height=95%} + +Dependency Inversion +------ + +* Um 2 Klassen zu entkoppeln kann ein Interface dazwischen geschalten werden. +* Das verhindert, dass eine High Level Komponente von einer Low Level Komponente abhängt. + +Dependency Inversion Beispiel 1 +----- + +![No dependency inversion](images/dependency-inversion/no-dependency-inversion.png) + +Dependency Inversion Beispiel 2 +----- + +![Dependency inversion](images/dependency-inversion/with-dependency-inversion.png){height=95%} + +Design Patterns +------- + + + +Factory (method) +------- + + + +Factory (method) Beispiel +------ + +```python +class CasiceConfigurationFactory: + def create(config): + if not config.is_casice_available(): + return CasiceConfiguration.not_available() + + if config.get_casice_host_override(): + return CasiceConfiguration.with_host(Observable.just(config.get_casice_host_override())) + + return CasiceConfiguration.with_host( + RetryUntilSuccess.create( + CasiceHostSupplier(config.get_casice_network_interface()), + Fibonacci.create(60).map(lambda i: 1000 * i) + ) + ) +``` + +AbstractFactory +------- + + + +AbstractFactory Beispiel 1 +------ + +```python +class ButtonFactory: + def get_bwd_button(self, name, dim, text): + return BwdButton(name, dim, text) + + def get_fwd_button(self, name, dim, text): + return FwdButton(name, dim, text) + + def get_info_button(self, name, info_text): + return InfoButton(name, info_text) + +class ButtonFactoryC(ButtonFactory): + pass + +class ButtonFactoryL(ButtonFactory): + pass +``` + +AbstractFactory Beispiel 2 +------ + +![footer_m](images/button_factory/footer_m.png) +![footer_c](images/button_factory/footer_c.png) + +Strategy +------- + + + +Strategy Example +------ + +```python +class RouteNumberResolveStrategy: + """ + Map the route number for lookup in assortment (menu structures, favorites, ...). + """ + def resolve(self, route_number): + pass + + +class IdentityRouteNumberStrategy(RouteNumberResolveStrategy): + def resolve(self, route_number): + return route_number + +class OrganisationPrefixRouteNumberStrategy(RouteNumberResolveStrategy): + def __init__(self, organisation_supplier): + self.organisation_supplier = organisation_supplier + + def resolve(self, route_number): +``` + +Observer +------- + + + +Observer Beispiel +------- + +```java +public class CarouselProvider { + + private final Set carouselObservers = Collections.synchronizedSet(new HashSet<>()); + private List files = new ArrayList<>(); + private int counter = 0; + + public void addCarouselObserver(CarouselObserver observer) { + carouselObservers.add(observer); + observer.fileChanged(getCurrentImage()); + } + + public void removeCarouselObserver(CarouselObserver observer) { + carouselObservers.remove(observer); + } +} +``` + +Command +------- + + + +Zusammenfassung Abstraktion +------ + +* Mit Abstraktionen + * \+ + * Strukturieren wir den Code + * Geben Konzepten (Abläufen, Konstanten) einen Namen + * Verstecken Details vor höheren Abstraktionsebenen + * Machen den Code DRY + * \- + * Erhöhen wir die Komplexität + * Verstecken Funktionalität + * Koppeln die User der Abstraktion + +Zusammenfassung +------ + +* High Level Abstraktionskonzepte + * DRY + * KISS + * YAGNI + * NIH +* SOLID Principles + * Single Responsibility Principle + * Open Closed Principle + * Liskov's Substition Principle + * Interface Segregation Principle + * Depdendency Inversion Principle +* Design Patterns + * Factory (method) + * AbstractFactory + * Strategy + * Observer + * Command