Home‎ > ‎

OOP Tips

  • Model the verbs first, then the nouns
    Design classes that represent requests, transfers, responses and so-on. Don't begin by creating classes for customer, employee, vehicle until your progress with the verbs has made it clear what properties and behavior needs to be in them, and what doesn't

  • Model the solution, not the feature
    If you're making classes like ReportPrinter or ProfitCalculator then you're doing it wrong. You want a Report class and a Printer class, or a Ledger class and a ProfitAndLossReport class

  • The domain is not reality
    A "domain" is an imaginary representation of the problem for the sake of constructing a solution. Sometimes you don't need an object for "person" if "transaction" is what you're really dealing with. Unless you're writing a sequel to The Sims, you will want to model an abstract world that has been contrived to represent what the software is meant to do

  • Report.ToPrinter() is wrong, Report.ToPostscript() is right
    Do not do Report.ToPrinter(Laserjet), do Laserjet.Print(Report)and have the Print() method accept an object that implements a ToPostscript (or ToPDF) interface. Data classes should have methods that serialize their contents to well-known intermediate formats (strings, csv, XML and so-on) in order to write themselves to output devices. They shouldn't have code that's specific to any particular output device

  • Customer.ToPostscript() is wrong, Laserjet.Print(new CustomerReport(Customer)) is right
    The more abstract the concept is, the less it should assume how it'll be used. A Report can presume to be printed and need a way to render itself in a common printer format, but a Customer object could be used for far more. Create classes that wrap abstract data to give them concrete purposes on demand

  • Inheritance is not how you reuse code
    Inheritance is how you show "is a" relationships in your taxonomy of data, it only accidentally has the benefit of reusing code. Do not create abstract classes just to cram utility functions in them. Inheritance is a mechanism that enables ways of code reuse by boiling down the essentials of a data-type to the minimum a particular method needs to work with it. For example, the next tip:

  • public void Print(Report myReport) is wrong, public void Print(IPrintable myDocument) is right
    Don't be afraid to use as many interfaces as you need to show the ways an object can interact with other parts of the program. Interfaces should also be as minimal and specific as possible, and it is not unusual for some classes to implement as many as 5 or 6 of them

  • Car does not inherit from ParkingLot
    Only use inheritance if the descendant class is truly a kind of parent class. If you're trying to join the two concepts together, consider using composition so that ParkingLot has a collection of Car objects

  • Methods hate state
    Unless it imports, exports, or modifies the state of an object, every class's methods should be given what they need only as parameters in the method call. Do not do private void ProcessData(), do private bool ProcessData(out IEnumerable input), even if input is a property or field of the same class. During refactoring, you'll figure out where ProcessData should really live, and it'll be easier to move it there

  • Classes shouldn't be peg-boards
    Publicly gettable properties are fine, and in some cases it's also necessary to have a publicly settable property, too. But before you make too many of them, think about whether the value of the property can be passed in the constructor or calculated from other data the class already has

  • Look for opportunities to keep it static
    Many persistence layers (filesystems, databases, etc) already do caching for you, so why cache it again in the state of an instantiated object? Look for situations where it makes sense to have a static class with get/set methods that take the entity's ID as a parameter. Here are some examples:
    • Counters that return/update an integer value to disk (the filesystem or drive controller will likely store the entire file in cache)
    • Lookup / configuration tables (your DBM will cache the most frequently requested rows)
    • Entities that don't have children, or aren't part of a tree or graph structure

  • Dogs should know it's not bacon
    Input validation should be performed within the property setters of the objects that are encapsulating the input (and not the constructors), but they should only use the resources that are immediately available to them for it; ie: don't stash database connections around in your entity classes just so you can check a lookup table; in such cases you'd pass that back to another layer

  • Tables are not classes
    As convenient as it may be to have ActiveRecords, you should not just run an ORM over all your database tables and call it a day. The most appropriate relational model for your data will not fit the most appropriate object model for the same data. Your class for Order should not just contain the same properties as your orders table has for columns, it should know what constitutes a valid order (do the totals add up?) and what state it's in (pending, paid, shipped, etc). It may even hide data that's visible in the database but out-of-scope for the program, yet expose data that's in a separate store and is in scope
    • Use an ORM that makes partial classes
      LINQ-to-SQL and other Object-Relational Mappers will generate a class for every table, but those classes will be partial. Start putting relevant logic and Law-Of-Demeter shortcuts into your custom side of the partial class