PHP Objects, Patterns and Practice (3rd edition)

(Barry) #1
CHAPTER 6 ■ OBJECTS AND DESIGN

Choosing Your Classes


It can be surprisingly difficult to define the boundaries of your classes, especially as they will evolve with
any system that you build.
It can seem straightforward when you are modeling the real world. Object-oriented systems often
feature software representations of real things—Person, Invoice, and Shop classes abound. This would
seem to suggest that defining a class is a matter of finding the things in your system and then giving
them agency through methods. This is not a bad starting point, but it does have its dangers. If you see a
class as a noun, a subject for any number of verbs, then you may find it bloating as ongoing
development and requirement changes call for it to do more and more things.
Let’s consider the ShopProduct example that we created in Chapter 3. Our system exists to offer
products to a customer, so defining a ShopProduct class is an obvious choice, but is that the only decision
we need to make? We provide methods such as getTitle() and getPrice() for accessing product data.
When we are asked to provide a mechanism for outputting summary information for invoices and
delivery notes, it seems to make sense to define a write() method. When the client asks us to provide the
product summaries in different formats, we look again at our class. We duly create writeXML() and
writeXHTML() methods in addition to the write() method. Or we add conditional code to write() to
output different formats according to an option flag.
Either way, the problem here is that the ShopProduct class is now trying to do too much. It is
struggling to manage strategies for display as well as for managing product data.
How should you think about defining classes? The best approach is to think of a class as having a
primary responsibility and to make that responsibility as singular and focused as possible. Put the
responsibility into words. It has been said that you should be able to describe a class’s responsibility in
25 words or less, rarely using the words “and” or “or.” If your sentence gets too long or mired in clauses,
it is probably time to consider defining new classes along the lines of some of the responsibilities you
have described.
So ShopProduct classes are responsible for managing product data. If we add methods for writing to
different formats, we begin to add a new area of responsibility: product display. As you saw in Chapter 3,
we actually defined two types based on these separate responsibilities. The ShopProduct type remained
responsible for product data, and the ShopProductWriter type took on responsibility for displaying
product information. Individual subclasses refined these responsibilities.


■Note Very few design rules are entirely inflexible. You will sometimes see code for saving object data in an


otherwise unrelated class, for example. While this would seem to violate the rule that a class should have a


singular responsibility, it can be the most convenient place for the functionality to live, because a method has to


have full access to an instance’s fields. Using local methods for persistence can also save us from creating a


parallel hierarchy of persistence classes mirroring our savable classes, and thereby introducing unavoidable


coupling. We deal with other strategies for object persistence in Chapter 12. Avoid religious adherence to design


rules; they are not a substitute for analyzing the problem before you. Try to remain alive to the reasoning behind


the rule, and emphasize that over the rule itself.

Free download pdf