Motley says: "A good design is all in the eye of the beholder" (Part 2)
Summary
Motley: Judging a design to be "good" is very subjective.
Maven: "Good" designs satisfy fundamental design principles, like separating creation from usage, encapsulating variability, preferring containment to inheritance, and designing to interfaces.
______________________________
[Context: Maven and Motley are at lunch, picking up where they left off with their discussion on design principles]
Maven: So, um, where, um, were we?
Motley: Stop talking with your mouth full, man! You know how annoying that is?
Maven: <Gulp>. Sorry about that. You know how I like to talk! Anyway, I think we left off with the DRY principle, for "Don't Repeat Yourself". Another good principle to abide by is to separate creation from use.
Motley: You mean like giving up your baby for adoption?
Maven: Oh, that was just a really bad joke - I'll pretend it never existed. Have you ever heard of the abstract factory pattern? That pattern exists to satisfy this principle. The idea is that you really don't want to have "new" littering your code base, particularly for objects that are created based on some dynamic logic or have some complicated initialization. By decoupling object instantiation from the actual usage of the object, the client can remain in the dark as to what kind of object it is actually using, helping us to adhere to our earlier principle of loose coupling.
Motley: Although, I guess if I only use an object one time and it is simple, there is no harm in creating it directly and not using a factory.
Maven: You bet. This is almost a guideline instead of a principle, but just use it intelligently and remember there are always exceptions. Another principle we should talk about is encapsulating variability. You want to examine your domain and when creating your design find what is common and what varies. Do something called "Commonality-Variability Analysis". You create abstractions from what is common and derive the variabilities from these common abstractions.
Motley: So with our development team, the commonalities between us would be that we're all Developer abstract base classes with a Code() method, and the variabilities would be that most of us are GreatDeveloper subclasses and that you are an AnnoyingDeveloper subclass.
Maven: I am sensing a high level of respect here. Yes, you are basically correct in the semantics of your example. The next principle is prefer containment over inheritance. With deep inheritance hierarchies you are tightly coupled to base implementations at run-time, creating a kind of "white box" reuse. It is hard to change behavior without recompiling. Alternatively, with containment, you can replace object instances at run-time to change behavior, reuse is more "black box" (i.e. you only need to know about the interface), and you are only coupled to an interface - that's it.
Motley: In my previous example, I'll hold a pointer to a Developer class, which is just an interface, and as a client I'll never really know whether I'm referring to an AnnoyingDeveloper like you or a GreatDeveloper like me. This is particularly true if I create it through a Factory or something that creates me the right one via a configuration file or some other dynamic mechanism.
Maven: You are a quick study, as I keep saying! The last principle I want to talk about is design to interfaces. You can probably guess from the other principles that this one was coming, but it's worthy of pointing out since it is so important. Having components in your design communicate through well-defined interfaces has many advantages, including being able to replace implementations as needed and decoupling as much as possible, both at build-time and run-time. This technique also has fantastic testability advantages - I can replace any implementation with a mock object that helps me write lightning quick tests which, as we discussed, is a key property of unit tests.
Motley: Well-defined interfaces, kind of like the way my fist fits squarely in your gut <pow!>. I am not quite sure what a "mock object" is, but I am guessing your standard response applies "we'll talk about that later."
Maven: Ugh. Thanks for the shot to the stomach - much appreciated <sigh>. And yeah, we'll talk about mocks later. <Cough> I think you knocked the wind out of me! Anyway, if you can put all those principles together, you are well on your way to a "good" design. It doesn't guarantee it, but you'll have much higher odds.
Motley: So to summarize, here is what you are saying are the design principles that go into making a design "good":
- Loose coupling
- High cohesion
- Simplicity
- No undesirable redundancy
- Separate creation from use
- Encapsulating variability
- Prefer containment to inheritance
- Design to interfaces
______________________________
Maven's Pointer: If you just think about making your designs testable, you'll find that you will automatically satisfy many of these principles. A testable design needs to be interface-oriented (for mocks), allow creation of object instances for testing, loosely coupled (or tests become difficult to write), and take advantage of containment (to replace instances).
Maven's Resources:
- Commonality-Variability Analysis, from the NetObjectives July 2004 Ezine
- Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Addison-Wesley, ISBN: 0201633612, 1995.