SOLID Design II PDF
Document Details
Uploaded by NiceRubidium
Tags
Summary
These lecture notes cover the SOLID design principles, focusing on Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. The notes include examples and explanations related to software development.
Full Transcript
1 SOLID Design II CSCI 2134: Software Development Agenda Lecture Contents SOLID: Liskov Substitution Principle SOLID: Interface Segregation Principle SOLID: Dependency Inversion Principle Brightspace Quiz Readings: This Lecture: Chapter 5 Next Lecture: Chapter 5...
1 SOLID Design II CSCI 2134: Software Development Agenda Lecture Contents SOLID: Liskov Substitution Principle SOLID: Interface Segregation Principle SOLID: Dependency Inversion Principle Brightspace Quiz Readings: This Lecture: Chapter 5 Next Lecture: Chapter 5 3 SOLID – Liskov Substitution Principle (LSP) We are out of cars, but… !!!! Vehicle Vehicle 9 SOLID – Liskov Substitution Principle (LSP) Principle: Objects must be replaceable by instances of subtype. “Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.” – Wikipedia The “is a” relationship must be preserved. A variant of “Design by Contract”: Preconditions cannot be strengthened by a subtype Postconditions cannot be weakened by a subtype Invariants of the supertype must be preserved in a subtype History constraint: New or modified members of the subclass should not modify the state of an object in manner not permitted by the base class. Purpose: reduces coupling and rigidity 10 SOLID – Liskov Substitution Principle (LSP) Notes: If subtypes do not meet the contract of a supertype, then your code must check what concrete type it is using LinkedList This usually means there is something wrong with the class + append(Object) hierarchy Example of an LSP violation: You have a LinkedList class that has an append() method that adds to the end of the list SortedLinkedList You create a SortedLinkedList subclass of LinkedList. + append(Object) The append() method either must no longer append to the end of the list or needs to be disabled. Either way, your code cannot use the append() method in the same way on both types of classes 11 SOLID – Liskov Substitution Principle (LSP) Code Smell: You are differentiating between objects of one type and their LinkedList subtypes. Having to use “instance of” mechanics to detect type + append(Object) Subtypes cannot reduce behaviour of parent type. Must increase behaviour. Method of subclass unconditionally throws exception. The SortedLinkedList method does not want to be called. + append(Object) 12 Example of LSP Violation public abstract class Vehicle { Vehicle protected boolean engineRunning; # engineRunning : boolean public abstract void startEngine(); + startEngine() : void public abstract void stopEngine(); + stopEngine() : void + move() : void public void move() { if (engineRunning) { System.out.println("I'm moving!"); } } } Bicycle Car All subclasses + startEngine() : void + startEngine() : void must implement + stopEngine() : void + stopEngine() : void these. + move() : void + move() : void 13 Example of LSP Violation public class Car extends Vehicle { public class Bicycle extends Vehicle { @Override @Override public void startEngine() { public void startEngine() { engineRunning = true; // Hmm... } // I don't have an engine } @Override public void stopEngine() { @Override engineRunning = false; public void stopEngine() { } // Hmm... } // I don't have an engine } } Fixing the LSP Violation Vehicle public abstract class Vehicle { + move() : void public abstract void move(); } public abstract class PoweredVehicle extends Vehicle { protected boolean engineRunning; public abstract void startEngine(); UnpoweredVehicle PoweredVehicle public abstract void stopEngine(); + move() : void # engineRunning : boolean public void move() { startEngine(); + startEngine() : void if (engineRunning) { + stopEngine() : void System.out.println("Vroom I'm moving!"); Bicycle + move() : void } stopEngine(); } } Car public abstract class UnpowerVehicle extends Vehicle { + startEngine() : void public void move() { + stopEngine() : void System.out.println("Vroom I'm moving!"); } 15 } Fixing the LSP Violation (cont.) public class Car extends public class Bicycle extends PoweredVehicle { UnpoweredVehicle { @Override // Related methods go here. public void startEngine() { } engineRunning = true; } @Override public void stopEngine() { engineRunning = false; } } SOLID – Interface Segregation Principle (ISP) 17 SOLID – Interface Segregation Principle (ISP) Principle: Keep interfaces small and client-specific. “Many client-specific interfaces are better than one general-purpose interface.” - Design Principles and Design Patterns, Robert Martin “No client should be forced to depend on methods it does not use.” - Wikipedia Purpose: reduces coupling Notes: Keep interfaces small and concise prevents unnecessary dependency creation (coupling) and therefore makes code easier to change. If you give mediocre programmers more stuff in the interface than needed, they will take it, increasing coupling between modules. They will also append to existing interfaces over making new ones. Do not allow this. - Rob Hawkey 18 SOLID – Interface Segregation Principle (ISP) Code Smell: Cycles or loops of dependency are Queue Stack indicators of interface segregation principle violations + append(Object) + prepend(Object) Interface contains method(s) not + removeFirst() : Object + removeFirst() : Object related to its function + prepend(Object) Example of an ISP violation: Your LinkedList class implements a Queue interface You need a stack. LinkedList Since you already have removeFirst() in the Queue interface, you add a prepend() to expand the interface to a stack The right thing to do would have been to create a new Stack Interface 19 Example of ISP Violation public class AllInOnePrinter public class Printer implements ISmartDevice { implements ISmartDevice { public void print() { public void print() { System.out.println("Printing!"); System.out.println("Printing!"); } } public void fax() { public void fax() { System.out.println("Faxing!"); throw new NotSupportedException(); } } public void scan() { public void scan() { System.out.println("Scanning!"); throw new NotSupportedException(); } } } } public interface ISmartDevice { public void print(); Printer has to provide the fax() public void fax(); and scan() method even though public void scan(); it does not do either. } Fix the ISP Violation public class AllInOnePrinter implements IFax, IPrinter, IScanner { public interface IPrinter { public void print(); public void print() { } System.out.println("Printing!"); } public interface IFax { public void fax(); public void fax() { } System.out.println("Faxing!"); } public interface IScanner { public void scan(); public void scan() { } System.out.println("Scanning!"); } } public class Printer implements IPrinter { public void print() { System.out.println("Printing!"); Printer has to provide the print() } } SOLID – Dependency Inversion Principle (DIP) Principle: Classes should depend on interfaces (abstract classes), not implementations. “One should depend on abstractions, not concretions.”, Design Principles and Design Patterns, Robert Martin High-level classes (orchestrator of many low-level classes) Class A Class A should not depend on concrete low-level classes. Abstractions should not depend on details (concrete objects). Purpose: reduces coupling and improves flexibility Class B Interface K Concretely: Bad: class A depends on class B Good: class A depends on Interface K and class B implements K Class B 23 A Concrete Comparison Bad Good LinkedList is a concrete class List is an interface import java.util.LinkedList; import java.util.List; class Lister { class Lister { private LinkedList list; private List list;...... public LinkedList public List merge(List add) { merge(LinkedList add) {...... } } } } Use abstract data types where possible, not specific implementation. 24 SOLID – Dependency Inversion Principle (DIP) Code Smell: ECommerce Many concrete classes, few abstract classes or interfaces Classes inheriting from concrete classes rather than abstract Classes depending on protected variables in super classes ISendMail Example of an ISP violation: From the ecommerce example earlier: ECommerce Want to change how emails are sent to customers MailGun SMTP Ecommerce class depends on abstract interface for “SendEmail” Ecommerce class doesn’t need to change, We can swap out one implementation with another MailGun SMTP Ecommerce has to change 25 Example of DIP Violation public class User { Variables are public class Database { public int id; part of the public public int saveUser(User user) { public String firstName; interface and // Some DB code to save the user out public String lastName; // to the DB and generate a unique ID public String email; cannot change return id; } public User(String firstName, String lastName, String email) { public int loadUser(int id, User user) { this.firstName = firstName; // Some DB code to load the user this.lastName = lastName; // from the DB this.email = email; //... Database db = new Database(); user.firstName = dbReader("firstName"); id = db.saveUser(this); user.lastName = dbReader("lastName"); } User assumes user.email = dbReader("email"); there is a specific } public User(int id) { Database class} Database assumes there this.id = id; is a specific User class Database db = new Database(); with public variables db.loadUser(id, this); } } Example of Fix to DIP Violation public class User { Variables are not public class Database implements IUserPersistence { private int id; public int saveUser(User user) { private String firstName; part of the public // Some DB code to save the user out private String lastName; interface // to the DB and generate a unique ID private String email; return id; } public User(String firstName, String lastName, String email, public int loadUser(int id, User user) { IUserPersistence p) { // Some DB code to load the user from the DB this.firstName = firstName; //... this.lastName = lastName; User uses an user.setFirstName(dbReader("firstName")); this.email = email; interface to store user.setLastName(dbReader("lastName")); id = p.saveUser(this); user.setEmail(dbReader("email")); } data } } Database uses setters to manipulate public User(int id, IUserPersistence p) { this.id = id; the User object p.loadUser(id, this); } public interface IUserPersistence { public void saveUser(User user); // getters and setters for private instance public void loadUser(int id, User user); // variables } } Example of Even Better Fix to DIP Violation public class User implements IUser{ public class Database implements IUserPersistence { private int id; public int saveUser(User user) { private String firstName; Variables are not // Some DB code to save the user out private String lastName; part of the public // to the DB and generate a unique ID private String email; interface return id; } public User(String firstName, String lastName, String email, public int loadUser(int id, IUser user) { IUserPersistence p) { // Some DB code to load the user from the DB this.firstName = firstName; //... this.lastName = lastName; User uses an user.setFirstName(dbReader("firstName")); this.email = email; interface to store user.setLastName(dbReader("lastName")); id = p.saveUser(this); user.setEmail(dbReader("email")); } data } } Database methods get an object of public User(int id, IUserPersistence p) { this.id = id; type IUser (interface) to set p.loadUser(id, this); } public interface IUserPersistence { public void saveUser(User user); // getters and setters for private instance public void loadUser(int id, User user); // variables } } Spectrum of Dependency Inversion Principle Minimum (This is where you start) Classes interact through interfaces / abstractions Middle of the road (Medium Isolation): Classes interact through interfaces / abstractions No class should subclass from a concrete class Use creational patterns (E.g. Factory Pattern) Extreme (Full Isolation): The type of all member variables must be interfaces or abstract classes All classes must connect only through interfaces or abstract classes No class should subclass from a concrete class No method should override an implemented method Use creational patterns for member variables Mediocre programmers will not follow these rules, too much work and too hard for them 29 Key Points SOLID is a set of object-oriented design principles intended to reduce complexity The Liskov Substitution Principle states that an object of a given type should be replaceable by any object of a subtype. The Interface Segregation Principle states that interfaces should be kept small and specific to the clients The Dependency Inversion Principle States that classes should depend on interfaces, not concrete implementations 30 Image References Retrieved January 29, 2020 http://pengetouristboard.co.uk/vote-best-takeaway-se20/ Image from StackOverflow, attributing it to https://www.coursera.org/lecture/object-oriented-design/1-3-1-coupling-and-cohesion-q8wGt https://library.kissclipart.com/20181002/cae/kissclipart-printer-icon-clipart-printer-computer- icons-clip-a-4355e69e2147b9a3.png http://clipart-library.com/printer-cliparts.html https://lh3.googleusercontent.com/proxy/Bln9JbfTJUZLqFBN6I5cBSMl6ww_z31qieplSeIlFzI3y7tM vFUIyPtWt-2HGgx3DGSYRC6lD-PmfiFz7Qc1XodvqTTmArCdcg https://lh3.googleusercontent.com/proxy/EjmkwGKjVndigDIQa4BpI6eQHDucnKJJ47EVJCLlHgP0c3 SX16aHum4kGVL0Q6uEQrc1gaGh6jD7lpPMYm2GQSoAT3L_RQfQ6_nw https://cdn3.vectorstock.com/i/1000x1000/56/72/car-rental-car-for-rent-word-on-red-ribbon- vector-26835672.jpg Retrieved November 25, 2020 https://lostechies.com/derickbailey/2009/02/11/solid-development-principles-in-motivational- pictures/ 31