In general, in the name of simplicity, it is best to make a class do one thing and one thing only. In reality, however, a class is typically laden with more than one duty. For example, a common implementation of an array-based stack would not only manage the stack operations, but would also manage the array--for example, it may have to grow the array if too many objects are pushed onto it. However, as more methods get added to the stack and as other classes, like a queue, get created that also need to manage a dynamic array, the opportunity for defects increases dramatically.
Similarly, when a particular duty needs to be synchronized as a whole, this can become difficult if it is interwoven with other duties. Code quickly becomes too complex when different locks are placed throughout the whole class, protecting this member and that, this code fragment and that. The opportunity to deadlock or fail to lock will become great.
break the class into a group of classes. Each class would be responsible for one duty or a related set of duties. In the example above, one would RefactorMercilessly
the array management code out of the stack and queue into a vector. Not only is the separate class simpler, easily verifiable and reusable; but it provides a good set of names for the set of actions it contains leading to SelfDocumentingCode
If one wanted a tighter grouping of classes, one could make the newer class a protected or private inner class of the original class; or one could make the newer class’s constructor protected or private and make the original class a friend of the newer. This way, only the original class may instantiate the newer class. Indeed, if the two classes are friends, and if you pass the this pointer of the original class into the newer class, then the newer class adds no functionality; it merely simplifies the code.
If taken to the extreme, one could effectively break the original class into separate classes for each of its duties and make the original class a mere FacadePattern
for the entire group.
Now that a given operation is separated into one contained object, it is trivial to synchronize it by wrapping a mutex/critical section around it in the original class. By using this kind of coarse-grain synchronization, race conditions almost totally disappear. The program doesn't fail to lock because everything is locked all at once. The program doesn't DeadLock
because two different locks rarely interact.
See also TightGroupOfClasses