Design(How-to) of classes containing collections of other classes

三世轮回 提交于 2019-12-04 07:04:51

There are many kinds of relationships - consider

  • Automobile and Wheels
  • Automobile and Driver
  • Automobile and Registered Owner
  • Customer and Order and Order Line
  • School and Class and instance of Class

If you look at UML modelling you'll see concepts such as Cardinality and Direction and distictions between Aggegration and Composition and questions relating to the life-cycle of the related objects.

It's then unsurprising that we need a range of techniques and patterns to deal with different kinds of relationships.

Concerning d). There's one overriding principle Law of Demeter or principle of least knowledge.

One important technique is then, Encapsulation decrease coupling by hiding information. Automobiles probably have little interest in many details of people, so we might have a IDriver interface on our Person class, IDriver offers the particular methods that Automobile cares about. The general principle being to favour programming to interfaces.

Following that through, we can think about a). Creation. As we're tending to use Interfaces, it often makes sense to use Factory patterns. That does leave the question of who calls the factory. Do we prefer:

   IPerson aPerson = myAutomobile.createDriver( /* params */);  

or

  IPerson aPerson = aPersonFactory.create( /* params */);
  myAutomobile.addDriver(aPerson);

Here I think it's pretty clear that Automobiles don't know much about people, and therefore the second is better division of responsibilities. However maybe Orders could reasonably create OrderLines, Classes create ClassInstances?

b). Keeping track? That's why we have rich sets of Collection classes. Which ones to use depend upon the nature of the relationship (one-one, one-many; etc.) and how we use it. So we pick Arrays and HashMaps etc. according to need. For a Car/Wheel we might even use names attributes of the Car - after all a Car has exactly six wheels (frontLeft, frontRight, backLeft, backRight, spare and steering). If by "store" you mean persist, then we're looking at techniques such foreign keys in a relational database. Mapping between RDBMS and in-memory objects is increasingly managed by nice persistence mechanisms such as JPA.

c). Audit? I've not seen auditing applied specifically at a relationship level. Clearly the automobile.addDriver() method may be arbitrarily complex. If there's a business requirement to audit this action, then it's pretty clear that this a decent place to do it. This is just a standard OO design question revolving around who owns the information. General principle: "Do Not Repeat Yourself" so pretty clearly we don't want every object that calls addDriver() to need to remember to audit, hence it's Auto's job.

When designing software I find it useful to look at things from a type theoretic point of view and see where it leads me.

A WorkSpace is of type Project + Project^2 + Project^3... (meaning whatever is true of a list of projects is true of a WorkSpace)

Similarly,

A Project is of type Resource + Resource^2 + Resource^3...

A Resource is of type File + File^2 + File^3 ...

So in a language like C#† you might define you WorkSpace class thusly:

public class WorkSpace : IList<Project> //the important thing here is that you're declaring that things that are true for a list of Projects is true for a WorkSpace. The WorkSpace class may in fact do other stuff too...
{

}

and similarly for the other classes.

Then in your client code you'd use it like this:

foreach (var project in WorkSpace)
{
    //do stuff
}

or

Projects.Add(new Resource() { file1, file2, file3, file4, /* etc */});

Think about the types and their relationships first. Encapsulation is a bit of a low-level housekeeping concept. The principle there is to keep related code close together and unrelated code far apart (where far apart means separated by or behind some kind of boundary, e.g. a function or a class or whatever other boundary concept a language might offer).

†From your posting history I surmise that you are familiar with C# but this applies to other languages.

Creating good OO design is a bit of an artform, but there are some principals to keep in mind.

Think about how your users will use your interface. Who is your user? Is it just you, and for a specific purpose, or are you designing something for different clients. Interface design is hard, but think about real examples of how your users will actually want to use your interface. One good way to do this is by writing tests, which forces you to use your own stuff. Another way is to look at existing libraries that do the same thing and see how they are used.

Keep things simple, stupid. That is, keep them simple from the perspective of your callers. This applies to encapsulation, where you should only expose the internals of your implementation as needed. It applies to creating a consistent interface that is easy to understand. And it means you should avoid the trap of making an object for every conceivable class, making tons of attributes, and being as general as possible. This last point is an especially important one; what makes us good as developers is that we have the ability to speculate and abstract, but because of this, we often fall into the trap of making systems massively more complicated than they need to be.

Think about ownership. Each object will either be owned by another, meaning that it should be created and destroyed as part of that object, or it will be referenced by another. You should design your interface accordingly. Ownership is a form of coupling, but it is often the correct design. It certainly simplifies your interface, as well as the burden on your callers to maintain their own objects.

Though shalt know and use thine collection libraries. Know the collection libraries of whatever language / framework you are using, and use them. Also, try to make your own interfaces consistent with those libraries in terms of naming and behavior.

Concerning auditing. You can do some auditing (logging, keeping statistics) from within your classes, but if your auditing needs are complex (needing to know when your data changes), it might be better to use an Observer pattern.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!