Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Solid Deconstruction

527 Aufrufe

Veröffentlicht am

Presented at DevSum (31st May 2018)

The SOLID principles are often presented as being core to good code design practice. Each of S, O, L, I and D do not, however, necessarily mean what programmers expect they mean or are taught. By understanding this range of beliefs we can learn more about practices for objects, components and interfaces than just S, O, L, I and D.

This talk reviews the SOLID principles and reveals contradictions and different interpretations. It is through paradoxes and surprises we often gain insights. We will leave SOLID somewhat more fluid, but having learnt from them more than expected.

Veröffentlicht in: Software
  • Als Erste(r) kommentieren

Solid Deconstruction

  1. 1. S O L I D
  2. 2. Single Responsibility Open-Closed Liskov Substitution Interface Segregation Dependency Inversion
  3. 3. Single Responsibility Open-Closed Liskov Substitution Interface Segregation Dependency Inversion
  4. 4. In object-oriented programming, the single responsibility principle states that every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. http://en.wikipedia.org/wiki/Single_responsibility_principle
  5. 5. The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. http://en.wikipedia.org/wiki/Single_responsibility_principle
  6. 6. The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. http://en.wikipedia.org/wiki/Single_responsibility_principle
  7. 7. The term was introduced by Robert C. Martin [...]. Martin described it as being based on the principle of cohesion, as described by Tom DeMarco in his book Structured Analysis and Systems Specification. http://en.wikipedia.org/wiki/Single_responsibility_principle
  8. 8. Cohesion is a measure of the strength of association of the elements inside a module. A highly cohesive module is a collection of statements and data items that should be treated as a whole because they are so closely related. Any attempt to divide them up would only result in increased coupling and decreased readability.
  9. 9. µονόλιθος
  10. 10. This is the Unix philosophy: Write programs that do one thing and do it well.Write programs to work together. Doug McIlroy
  11. 11. In McIlroy's summary, the hard part is his second sentence: Write programs to work together. John D Cook
  12. 12. In the long run every program becomes rococo — then rubble. Alan Perlis
  13. 13. We refer to a sound line of reasoning, for example, as coherent. The thoughts fit, they go together, they relate to each other. GlennVanderburg http://vanderburg.org/blog/2011/01/31/cohesion.html
  14. 14. This is exactly the characteristic of a class that makes it coherent: the pieces all seem to be related, they seem to belong together, and it would feel somewhat unnatural to pull them apart. Such a class exhibits cohesion. GlennVanderburg http://vanderburg.org/blog/2011/01/31/cohesion.html
  15. 15. #include <stdlib.h>
  16. 16. Every class should embody only about 3–5 distinct responsibilities. Grady Booch Object Solutions
  17. 17. One of the most foundational principles of good design is: Gather together those things that change for the same reason, and separate those things that change for different reasons. This principle is often known as the single responsibility principle, or SRP. In short, it says that a subsystem, module, class, or even a function, should not have more than one reason to change.
  18. 18. Single Responsibility Open-Closed Liskov Substitution Interface Segregation Dependency Inversion
  19. 19. Interface inheritance (subtyping) is used whenever one can imagine that client code should depend on less functionality than the full interface. Services are often partitioned into several unrelated interfaces when it is possible to partition the clients into different roles. "General Design Principles" CORBAservices
  20. 20. The dependency should be on the interface, the whole interface, and nothing but the interface.
  21. 21. This is exactly the characteristic of a class that makes it coherent: the pieces all seem to be related, they seem to belong together, and it would feel somewhat unnatural to pull them apart. Such a class exhibits cohesion. GlennVanderburg http://vanderburg.org/blog/2011/01/31/cohesion.html
  22. 22. This is exactly the characteristic of an interface that makes it coherent: the pieces all seem to be related, they seem to belong together, and it would feel somewhat unnatural to pull them apart. Such an interface exhibits cohesion.
  23. 23. public interface LineIO { String read(); void write(String lineToWrite); }
  24. 24. public interface LineReader { String read(); } public interface LineWriter { void write(String lineToWrite); }
  25. 25. Single Responsibility Open-Closed Liskov Substitution Interface Segregation Dependency Inversion
  26. 26. Concept Hierarchies The construction principle involved is best called abstraction; we concentrate on features common to many phenomena, and we abstract away features too far removed from the conceptual level at which we are working. Ole-Johan Dahl and C A R Hoare "Hierarchical Program Structures"
  27. 27. A type hierarchy is composed of subtypes and supertypes. The intuitive idea of a subtype is one whose objects provide all the behavior of objects of another type (the supertype) plus something extra. Barbara Liskov "Data Abstraction and Hierarchy"
  28. 28. What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. Barbara Liskov "Data Abstraction and Hierarchy"
  29. 29. https://msdn.microsoft.com/en-us/library/ms173147
  30. 30. E.g., Python's dict class, which is a hashed mapping container E.g., Python's OrderedDict class, which preserves order of insertion
  31. 31. public class RecentlyUsedList { ... public int Count { get ... } public string this[int index] { get ... } public void Add(string newItem) ... ... }
  32. 32. public class RecentlyUsedList { ... public int Count => ... public string this[int index] => ... public void Add(string newItem) ... ... }
  33. 33. public class RecentlyUsedList { private IList<string> items = new List<string>(); public int Count => items.Count; public string this[int index] => items[index]; public void Add(string newItem) { if(newItem == null) throw new ArgumentNullException(); items.Remove(newItem); items.Insert(0, newItem); } ... }
  34. 34. public class RecentlyUsedList : List<string> { public override void Add(string newItem) { if(newItem == null) throw new ArgumentNullException(); items.Remove(newItem); items.Insert(0, newItem); } ... }
  35. 35. namespace List_spec { ... [TestFixture] public class Addition { private List<string> list; [Setup] public void List_is_initially_empty() { list = ... } ... [Test] public void Addition_of_non_null_item_is_appended() ... [Test] public void Addition_of_null_is_permitted() ... [Test] public void Addition_of_duplicate_item_is_appended() ... ... } ... }
  36. 36. namespace List_spec { ... [TestFixture] public class Addition { private List<string> list; [Setup] public void List_is_initially_empty() { list = new List<string>(); } ... [Test] public void Addition_of_non_null_item_is_appended() ... [Test] public void Addition_of_null_is_permitted() ... [Test] public void Addition_of_duplicate_item_is_appended() ... ... } ... }
  37. 37. namespace List_spec { ... [TestFixture] public class Addition { private List<string> list; [Setup] public void List_is_initially_empty() { list = new RecentlyUsedList(); } ... [Test] public void Addition_of_non_null_item_is_appended() ... [Test] public void Addition_of_null_is_permitted() ... [Test] public void Addition_of_duplicate_item_is_appended() ... ... } ... }
  38. 38. What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. Barbara Liskov "Data Abstraction and Hierarchy"
  39. 39. What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. Barbara Liskov "Data Abstraction and Hierarchy"
  40. 40. What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. Barbara Liskov "Data Abstraction and Hierarchy"
  41. 41. What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. Barbara Liskov "Data Abstraction and Hierarchy"
  42. 42. Single Responsibility Open-Closed Liskov Substitution Interface Segregation Dependency Inversion
  43. 43. Bertrand Meyer gave us guidance as long ago as 1988 when he coined the now famous open-closed principle. To paraphrase him: Software entites (classes, modules, functions, etc.) should be open for extension, but closed for modification. "The Open-Closed Principle", Robert C Martin C++ Report, January 1996
  44. 44. Open for extension Closed for extension Closed for modification Open for modification Extend existing code without changing it, e.g., using a framework Extend or change existing code, e.g., open-source and non-published code Non-extensible and unchangeable code, e.g., composable code or legacy code Non-extensible code that can be maintained, e.g., composable code in a maintained library
  45. 45. The principle stated that a good module structure should be both open and closed: ▪ Closed, because clients need the module's services to proceed with their own development, and once they have settled on a version of the module should not be affected by the introduction of new services they do not need. ▪ Open, because there is no guarantee that we will include right from the start every service potentially useful to some client.
  46. 46. [...] A good module structure should be [...] closed [...] because clients need the module's services to proceed with their own development, and once they have settled on a version of the module should not be affected by the introduction of new services they do not need.
  47. 47. There is no problem changing a method name if you have access to all the code that calls that method. Even if the method is public, as long as you can reach and change all the callers, you can rename the method.
  48. 48. There is a problem only if the interface is being used by code that you cannot find and change. When this happens, I say that the interface becomes a published interface (a step beyond a public interface).
  49. 49. The distinction between published and public is actually more important than that between public and private. Martin Fowler https://martinfowler.com/bliki/PublishedInterface.html
  50. 50. With a non-published interface you can change it and update the calling code since it is all within a single code base. But anything published so you can't reach the calling code needs more complicated treatment. Martin Fowler https://martinfowler.com/bliki/PublishedInterface.html
  51. 51. [...] A good module structure should be [...] open [...] because there is no guarantee that we will include right from the start every service potentially useful to some client.
  52. 52. Speculative Generality Brian Foote suggested this name for a smell to which we are very sensitive. You get it when people say, "Oh, I think we need the ability to do this kind of thing someday" and thus want all sorts of hooks and special cases to handle things that aren't required.
  53. 53. extends
  54. 54. A myth in the object-oriented design community goes something like this: If you use object-oriented technology, you can take any class someone else wrote, and, by using it as a base class, refine it to do a similar task. Robert B Murray C++ Strategies andTactics
  55. 55. Design and implement for inheritance or else prohibit it By now, it should be apparent that designing a class for inheritance places substantial limitations on the class.
  56. 56. https://8thlight.com/blog/uncle-bob/2014/05/12/TheOpenClosedPrinciple.html
  57. 57. I've heard it said that the OCP is wrong, unworkable, impractical, and not for real programmers with real work to do. The rise of plugin architectures makes it plain that these views are utter nonsense. On the contrary, a strong plugin architecture is likely to be the most important aspect of future software systems. https://8thlight.com/blog/uncle-bob/2014/05/12/TheOpenClosedPrinciple.html
  58. 58. OCP ≡ Plug-ins?
  59. 59. OCP ≡ Plug-ins/
  60. 60. public abstract class Shape { ... } public class Square : Shape { ... public void DrawSquare() ... } public class Circle : Shape { ... public void DrawCircle() ... }
  61. 61. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) if (s is Square) (s as Square).DrawSquare(); else if (s is Circle) (s as Circle).DrawCircle(); }
  62. 62. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) if (s is Square) (s as Square).DrawSquare(); else if (s is Circle) (s as Circle).DrawCircle(); }
  63. 63. public abstract class Shape { ... public abstract void Draw(); } public class Square : Shape { ... public override void Draw() ... } public class Circle : Shape { ... public override void Draw() ... }
  64. 64. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) s.Draw(); }
  65. 65. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) s.Draw(); }
  66. 66. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) s.Draw(); }
  67. 67. Bertrand Meyer gave us guidance as long ago as 1988 when he coined the now famous open-closed principle. To paraphrase him: Software entites (classes, modules, functions, etc.) should be open for extension, but closed for modification. "The Open-Closed Principle", Robert C Martin C++ Report, January 1996
  68. 68. Bertrand Meyer gave us guidance as long ago as 1988 when he coined the now famous open-closed principle. To paraphrase him: Software entites (classes, modules, functions, etc.) should be open for extension, but closed for modification. "The Open-Closed Principle", Robert C Martin C++ Report, January 1996
  69. 69. This double requirement looks like a dilemma, and classical module structures offer no clue. But inheritance solves it. 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.
  70. 70. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) if (s is Square) (s as Square).DrawSquare(); else if (s is Circle) (s as Circle).DrawCircle(); }
  71. 71. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) if (s is Square) (s as Square).DrawSquare(); else if (s is Circle) (s as Circle).DrawCircle(); }
  72. 72. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) s.Draw(); }
  73. 73. public abstract class Shape ... public class Square : Shape ... public class Circle : Shape ... static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) s.Draw(); }
  74. 74. public sealed class Shape { public enum Type { Square, Circle } ... public void Draw() ... } static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) s.Draw(); }
  75. 75. public sealed class Shape { public enum Type { Square, Circle } ... public void Draw() ... } static void DrawAllShapes(Shape[] list) { foreach (Shape s in list) s.Draw(); }
  76. 76. Don't publish interfaces prematurely. Modify your code ownership policies to smooth refactoring.
  77. 77. Single Responsibility Open-Closed Liskov Substitution Interface Segregation Dependency Inversion
  78. 78. The principle states: A. High-level modules should not depend on low-level modules. Both should depend on abstractions. B. Abstractions should not depend upon details. Details should depend upon abstractions. https://en.wikipedia.org/wiki/Dependency_inversion_principle
  79. 79. Program to an interface, not an implementation. Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides
  80. 80. Program to an interface, not an instance.
  81. 81. One of the most foundational principles of good design is: Gather together those things that change for the same reason, and separate those things that change for different reasons. This principle is often known as the single responsibility principle, or SRP. In short, it says that a subsystem, module, class, or even a function, should not have more than one reason to change.
  82. 82. Write components that do one thing and do it well. Write components to work together. Program to an interface, not an implementation. Don't publish interfaces prematurely. Modify your code ownership policies to smooth refactoring.

×