Ece325lecture-21designprinciples

.pdf
School
University of Alberta**We aren't endorsed by this school
Course
ECE 325 325
Subject
Computer Science
Date
Dec 17, 2024
Pages
51
Uploaded by DukeRiverHamster41
ECE 325 Object-OrientedSoftware DesignLecture 21: Design principles
Background image
2Clean Code – Robert C. Martin Most of this lecture’s content comes from the Clean Code bookRobert C. Martin / “Uncle Bob”Link is on eClass
Background image
3The importance of good codeWriting code is one thingMaintaining code is much more difficultIt is much harder to maintain code of low quality (‘bad code’)Adding new functionality to bad code often leads to more bad code
Background image
4So how do we end up with bad code?Time crunch/rushingTired programmersOverloaded programmers...Can replace ‘programmer’ with student :)
Background image
5“We will fix it later...”Unfortunately, ‘later equals never’LeBlanc’s lawEven though it is not always our fault, bad code is always considered our faultDon’t be afraid to tell a manager the truth about timelines
Background image
6Symptoms of bad codeDifficult to change (rigidity)Leads to fear of making non-critical changesBreaks in many places every time it is changed (fragility)Inability to reuse code (immobility)When ‘good’ changes are harder to make than hacks (viscosity)
Background image
7The SOLID PrinciplesAcronym for five design principles to improve software designsSubset of the principles discussed by Uncle BobThese principles are not rules or lawsThey have been observed to work in many cases, but there is no proof that they always workThe principles are actually quite simpleWhich makes them so powerful
Background image
8S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
9S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
10Single responsibility principle (SRP)“A class should have only one reason to change.”This means a class should have only one job/responsibilityWe want our systems to be composed of many small classes, not a few large ones
Background image
11What is wrong with this design?Studentvoid study()int markExam(Exam e)boolean admit(Program p)
Background image
12What is wrong with this design?We probably should not let students mark their own exams, or decide about admissionStudentvoid study()int markExam(Exam e)boolean admit(Program p)
Background image
13This would be a better designIndeed, the responsibilities should be moved into separate classesThen the student can focus on their responsibilityStudentvoid study()Markerint markExam(Exam e, Student s)AdmissionOfficerboolean admit(Program p, Student s)
Background image
14Why the single responsibility principle mattersImproves cohesion in classesCohesion = how well things belong togetherMakes classes smallerEasier to maintain or testMakes classes easier to reuseSmaller building blocks are more versatile
Background image
15S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
16S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
17Open-closed principle (OCP)“A module should be open for extension but closed for modification”We should be able to change what a module does without changing its codePolymorphism is essential to achieve thisDynamic/runtime polymorphism (e.g., inheritance)Static/compile-time polymorphism (e.g., generics)
Background image
18An example of why OCP mattersWhat if we want to add a class Circle?publicclassRectangle {publicdoublelength;publicdoublewidth;}publicclassAreaCalculator {publicstaticdoublecalculateArea(Rectangle r) {returnr.length*r.width;}}
Background image
19This is a non-OCP way to do itpublicclassCircle {publicdoubleradius;}publicclassAreaCalculator {publicstaticdoublecalculateArea(Rectangle r) {returnr.length*r.width;}publicstaticdoublecalculateArea(Circle c) {returnMath.PI*c.radius*c.radius;}}
Background image
20Why is this not a good approach?publicclassCircle {publicdoubleradius;}publicclassAreaCalculator {publicstaticdoublecalculateArea(Rectangle r) {returnr.length*r.width;}publicstaticdoublecalculateArea(Circle c) {returnMath.PI*c.radius*c.radius;}}
Background image
21Why is this not a good approach?For every type of shape we add, we need to change the AreaCalculator classSo this design is not closed for modificationWe can use polymorphism to fix this
Background image
22OCP in actionpublicinterfaceShape {publicabstractdoublecalculateArea();}publicclassRectangle implementsShape {privatedoublelength;privatedoublewidth;@OverridepublicdoublecalculateArea() {returnlength*width;}}
Background image
23OCP in actionpublicclassAreaCalculator {publicstaticdoublecalculateArea(Shape s) {returns.calculateArea();}}
Background image
24This implementation follows theopen-closed principleIt is open for extensionWe can add any type of new Shape we want by adding a class that implements ShapeAreaCalculator or Shape do not have to be modified when we add a new shape
Background image
25S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
26S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
27Liskov substitution principle (LSP)“Subclasses should be substitutable for their base classes.”A user of a base class should continue to function properly if a subclass of that class is passed to itNamed after Prof. Barbara Liskov (MIT)Won a Turing award in 2008 for her contributions to computer programming languages
Background image
28LSP in practiceCarFormulaOneCarRaceTrackhas-aIf RaceTrack takes a Car object...It should be legal to use an instance of FormulaOneCar in RaceTrack as well
Background image
29But, what about this?A MiniVan should not be on the race track!This is a violation of the LSP and indicates a design flawCarFormulaOneCarRaceTrackhas-aMiniVan
Background image
30But, what about this?How can we fix the design flaw while still allowing different types of cars on the RaceTrack?CarFormulaOneCarRaceTrackhas-aMiniVan
Background image
31We need to add an interface RaceCar to distinguish cars that can and cannot raceRaceCarFormulaOneCarRaceTrackhas-aMiniVanCar
Background image
32We need to add an interface RaceCar to distinguish cars that can and cannot raceRaceCarFormulaOneCarRaceTrackhas-aMiniVanCarNow, we cannot add a Car to RaceTrack anymore! (If you are unsure: follow the direction of the arrows)
Background image
33Alternative solution: Make RaceCar a subclass of CarRaceCarFormulaOneCarRaceTrackhas-aMiniVanCar
Background image
34How do we fix LSP-related issues?LSP applies to a contract between classese.g., a RaceTrack has a contract with the cars on it that they can raceFactoring out common features into a separate class can help create an LSP-proof hierarchye.g., by adding a RaceCar interface that all cars that can race must implement
Background image
35S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
36S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
37Interface segregation principle (ISP)“Many client specific interfaces are better than one general purpose interface.”Can also be read as “No client should be forced to depend on methods it does not use.”If you have a service that is used by several classes, create specific interfaces for each class
Background image
38A non ISP-proof exampleService// methods for A..// methods for B..// methods for C..ClassCClassBClassA
Background image
39Why is this problematic?The classes can call methods that were not intended for themEven worse, if Service is an interface, classes must implement irrelevant methodsThis would result in lots of UnsupportedOperationExceptionsWhich in turn would violate the Liskov Substitution Principle
Background image
40An ISP-proof exampleClassCClassBClassA<<interface>>ServiceA// methods for A..<<interface>>ServiceB// methods for B..<<interface>>ServiceC// methods for C..Service// methods for A..// methods for B..// methods for C..
Background image
41How do we fix ISP-related issues?We ‘segregate’ the functionality into smaller interfaces by extracting only the relevant methodsNote that this also fixes the LSP violation
Background image
42S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
43S.O.L.I.D.Single responsibility principleOpen-closed principleLiskov substitution principleInterface segregation principleDependency inversion principle
Background image
44Dependency inversion principle (DIP)“Depend upon abstractions. Do not depend upon concretions.”Higher-level classes should depend on abstractions, and not on the details of lower-level classes directly
Background image
45A real-life example of DIP
Background image
46A real-life example of DIP In this example, the Lamp is a higher-level class that depends on a lower-level power sourceBut, we do not really care where the power is coming fromSo the Lamp should depend on an abstract power source, rather than on the low-level details (= soldering directly into the outlet)
Background image
47The Lamp example in OOPClearly, this leads to a very restrictive type of LampWhat if we move the lamp? We could no longer use itNB in a procedural design, this dependency on lower-level classes is actually very commonLampParticularOutletInCPsHouse
Background image
48The Lamp example in OOP, DIP-proofThe lamp now depends on an abstraction of the power source, instead of on a concrete classWe ‘inverted’ the dependency from low-level to high-levelLamp<<interface>>PowerSourceOutlet
Background image
49Dependency inversion allows us to reduce coupling between classesCoupling indicates how closely two classes are tied togetherHigh coupling means that making a change in one class is likely to require a change in the other class By depending on abstractions, we reduce coupling, since they take away details about the implementationNote that you gain a lot in terms of flexibility – our program can now use any type of PowerSource
Background image
50Another application of DIP in practiceThis principle is also the reason that we declare objects as ‘the most general/abstract’ typeIt leads to more flexibility later onRaceCar rc = newFormulaOneCar();
Background image
51S.O.L.I.D. summarizedKeep in mind: these principles are not lawsYou should aim to follow them as much as possible... but maintain good judgmentMake sure understand the principles and opportunities for applying them will ariseThese should become very intuitiveWe will consider these principles as parts of the code quality/cleanliness points on assignments from here on
Background image