tdde45 lecture 3 design principles
play

TDDE45 - Lecture 3: Design Principles Martin Sjlund Department of - PowerPoint PPT Presentation

1 / 44 TDDE45 - Lecture 3: Design Principles Martin Sjlund Department of Computer and Information Science Linkping University 2020-09-09 2 / 44 Part I Single Responsibility Principle 3 / 44 Single Responsibility Principle - History


  1. 1 / 44 TDDE45 - Lecture 3: Design Principles Martin Sjölund Department of Computer and Information Science Linköping University 2020-09-09

  2. 2 / 44 Part I Single Responsibility Principle

  3. 3 / 44 Single Responsibility Principle - History The term “Single responsibility principle” was made popular by Martin 2003. SRP was coined a few years earlier in the late 90s and he said “A class should have only one reason to change”. There is a clarifjcation in Martin 2014 of what he meant by that.

  4. 4 / 44 Single Responsibility Principle The single responsibility principle states that every module, class, or function should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.

  5. 5 / 44 } The God object has too many responsibilities. Basically a procedural program } void renderGame(); // Render the game from the player's point of view void renderHUD(); // Heads-up display shows the player's life total, etc // ... Violating the SRP using a God object foreach (Enemy e : state.enemies) void advanceGame() { // Every object moves 1 frame State state; // The game's state class Game { disguised in OOP clothing.

  6. 6 / 44 // Render the game from player's point of view } hud.render(); scene.render(); state.advanceGame(); void loop() { } scene = new Scene(state.getPlayer(), state); hud = new HUD(state); First step towards the SRP state = new State(); public Game() { Scene scene; HUD hud; State state; // The game's state class Game { }

  7. 7 / 44 Part II Open-Closed Principle

  8. 8 / 44 Open-Closed Principle – History available for extension. For example, it should be possible to add fjelds to the data structures it contains, or new elements to the set of functions it performs. available for use by other modules. This assumes that the module has been given a well-defjned, stable description (the interface in the sense of information hiding). “ ◮ A module will be said to be open if it is still ◮ A module will be said to be closed if [it] is Meyer 1988 ”

  9. 9 / 44 Open-Closed Principle A class is closed, since it may be compiled, stored in a library, baselined, and used by client classes. But it is also open, since any new class may use it as parent, adding new features. When a descendant class is defjned, there is no need to change the original or to disturb its clients. In modern usage, code that adheres to the principle tends to use abstract classes or interfaces instead. At the time inheritance existed but the concept of abstract classes or interfaces had not quite been introduced in object-oriented programming. “ Meyer 1988 ”

  10. 10 / 44 Violating the Open-Closed Principle Square *s = dynamic_cast<Square>(shape) Rectangle *r = dynamic_cast<Rectangle>(shape) // Equally bad } return ((Rectangle*)shape)->area(); case RECTANGLE: return ((Square*)shape)->area(); case SQUARE: swicth (shape->t) { // Something using shapes } int area() { return length * length; } : Shape(SQUARE), length(length) {} Rectangle( int length) int length; class Square : Shape { } int area() { return height * width; } height(height) {} width(width), : Shape(RECTANGLE), Rectangle( int width, int height) int width; int height; class Rectangle : Shape { } int t; // 1==Square, 2==Rectangle Shape( int t) : t(t) {} class Shape { // How will this handle a new shape? r ? r->area() : s ? r->area() : 0;

  11. 11 / 44 int length; } if ( dynamic_cast <Square>(shape) { foreach ( auto shape : shapes) { // Or filtering shape->area(); // Something using shapes } int area() { return length * length; } : length(length) {} Rectangle( int length) class Square : Shape { Adhering to the Open-Closed Principle } int area() { return height * width; } height(height) {} : width(width), Rectangle( int width, int height) int width; int height; class Rectangle : Shape { } /* virtual */ int area(); class Shape { // Could be abstract class or interface } sum += shape->area();

  12. 12 / 44 Open-Closed Principle without Object-Orientation? abstract type Shape end struct Rectangle <: Shape width:: Int height:: Int end struct Square <: Shape length:: Int end area(s::Rectangle):: Int = s.width * s.height area(s::Square):: Int = s.length ^ 2 Multiple dispatch makes it easy to extend code. In Julia there typically exists an informal interface you need to add functions for (which is not checked by the compiler).

  13. 13 / 44 Part III Liskov Substitution Principle

  14. 14 / 44 Liskov Substitution Principle - History Initially introduced in a keynote address by Liskov 1987. The original text talks about type systems in object-oriented programming at the time (SmallTalk) and is rather hard to decipher what is the actual principle is. A later paper has a rather short description of what is now called the Liskov Substitution Principle: Subtype Requirement: Note that this property cannot be automatically checked by a compiler since it is related to semantics and expected behaviour of the object. “ Liskov and Wing 1994 ” Let φ ( x ) be a property provable about objects x of type T. Then φ ( y ) should be true for objects y of type S where S is a subtype of T.

  15. 15 / 44 Liskov Substitution Principle When we inherit from a class that is quite similar to what we want, we need to decide if we need a common ancestor (abstraction) to the two or if the subtype has proper subtype semantics of the parent.

  16. 16 / 44 Violating the Liskov Substitution Principle (1) } } set { } return _length; get { public override int width { } } _length = value ; set { } return _length; get { public override int height { private int _length; public class Square : Rectangle { } int area() { return height * width; } public virtual int width { get ; set ; } public virtual int height { get ; set ; } public class Rectangle { } _length = value ;

  17. 17 / 44 Violating the Liskov Substitution Principle (2) r.width = 4; // What is the area? Rectangle r = new Square(); r.height = 6;

  18. 18 / 44 Violating the Liskov Substitution Principle (2) // What is the area? 16 Rectangle r = new Square(); r.height = 6; r.width = 4;

  19. 19 / 44 Part IV Interface Segregation Principle

  20. 20 / 44 Interface Segregation Principle Clients should not be forced to de- pend upon interfaces that they do not use. Make fjne grained interfaces that are client specifjc. “ Martin 1996 ” “ Robert C. Martin ”

  21. 21 / 44 Interface Segregation Principle – History Xerox had created a new printer with many functions. This had resulted in a huge God class which supported these functions. See also, Single Responsibility Principle.

  22. 22 / 44 Violation of the ISP AbstractPrinter Document; void Print(Document d); Document Scan(); void Fax(Document d); MultiFunctionPrinter BasicPrinter void Print(Document d); void Print(Document d); Document Scan(); void Fax(Document d); "Needs to add dummy Scan and Fax functions that are not supported"

  23. According to the ISP 23 / 44 AbstractPrinter AbstractScanner AbstractFax Document; void Print(Document d); Document Scan(); void Fax(Document d); BasicPrinter AbstractMultiFunction void Print(Document d); Perhaps even extending the basic printer MultiFunctionPrinter void Print(Document d); Document Scan(); void Fax(Document d);

  24. 24 / 44 Isn’t this (ISP) the same thing as the SRP? Not quite. Go back and look at the solutions. The ISP splits adds interfaces for the smaller parts so that clients do not see the parts they are not interested in. Adhering to it is mostly a structural thing (moving code around). For the SRP, the underlying problem may be bigger and you will tend to change the code somewhat.

  25. 25 / 44 Part V Dependency Inversion Principle

  26. 26 / 44 Dependency Inversion Principle Martin 1995 is an early work describing dependency inversion. The inversion here being inverting how you write code: instead of creating client code that uses a concrete implementation such as a KeyboardReader, create a general Reader interface and tie that to the concrete implementation later on.

  27. 27 / 44 class Writer { Example of an interface that is probably }; virtual char Read() = 0; public : class Reader { }; virtual void Write( char ) = 0; public : interface changes. Dependency Inversion Principle updated when the implementations of this depends on interfaces will not need to be Which in turn means your code which implementation details). updated (because they do not depend on means dependencies will rarely need to be few to no dependencies is good. This It also generally says that interfaces having not going to change.

  28. 28 / 44 Part VI SOLID

  29. 29 / 44 Design Principles: SOLID SOLID Motivational Posters by Derick Bailey, used under CC BY-SA 3.0 US

  30. 30 / 44 Part VII Remarks

  31. 31 / 44 Principles + Problem = Pattern

  32. 32 / 44 Principles = SOLID + Some general tips

  33. 33 / 44 Some general tips 1. Encapsulate what varies (S) 2. Program to an interface, not to an implementation (I, D) 3. Favor Composition over Inheritance (L) 4. Don’t call us, we’ll call you (O)

Recommend


More recommend