Outline
Other Material
Our focus will be on how objects are connected to each other, and how one can make those connections as loose as possible.
Our primary tool for analyzing connectednesss will be the concepts of visibility and dependency.
Difficulties in developing large scale programs are often not so much a matter of algorithmic complexity as they are of communication complexity.
If several programmers are working together on a project, need to control the amount of information one programmer must have about the code being developed by a second programmer.
Visibility is an attribute of names.
Dependency describes the degree to which one software component relies on another component to perform its responsibilities.
A high degree of dependency obviously limits code reuse - moving one component to a new project.
Ideas from the Software Engineering Community, pre-dating OOP.
Arranged from Bad to Better
class SneekyModifier { public: void sneeky () { // change my friends name myFriend->name = "Lucy"; } Person * myFriend; }; | class Person { public: Person () { name = "Larry"; } string name; }; |
This is bad because it makes it difficult to understand a single class in isolation.
Can be mitigated by always making your data areas private or protected, and not exposing pointers to these areas.
double todaysDow; | |
class One { public: void setDow () { todaysDow = 9473; } }; | class Two { public: void printDow () { cout << "Today the Dow hit " << todaysDow; } }; |
Two or more classes that interact through a common global variable. Again, makes it difficult to understand a single class in insolation.
Can be mitigated by making a class that ``manages'' the global area, thereby reducing global coupling to component coupling.
This occurs when objects are linked by the fact that one must be manipulated before the other, but otherwise they have no connection.
Again, makes it difficult to understand a class in isolation.
Can be mitagated by making a controller class, that clearly indicates the sequence of operations.
class MyClass { public: doStuff () { doFirst(); doSecond(); doThird(); } protected: doFirst() { ... } doSecond() { ... } doThird() { ... } }
Occurs when one class holds an instance of another class.
class Set { ... private: List data; }
Ideally, connection is one way. Held component has no knowledge or holder.
This is a very weak and benign connection. (Weak is good, remember).
Parameter coupling occurs when one object knows of another only through being passed as a parameter or a return value.
Another very weak (and therefore good) type of coupling.
class MyClass { public: void doSomething (Set aSet) { ... } }
Subclass coupling describes the relationship between a parent class and a child class.
Ideally the parent has no strong connection to the child, so the connection is one way. Can understand the parent in isolation from the child.
class Parent { ... } class Child extends Parent { ... }
A very weak form of coupling. Which makes it a good design choice.
Also arranged from bad to better:
The law of demeter is an attempt to limit the way in which one component can interact with another component.
Law of Demter. In any Method M attached to a class C, only methods defined by the following classes may be used:
- 1.
- The instance variable classes of C.
- 2.
- The argument classes of method M (including C); note that global objects or objects created inside the method M are considered arguments to M.
Named for the Demeter project at Northeaster University.
Law of Demeter (weak form). Inside a method, it is only permitted to access or send messages to the following objects:
The strong form eliminates global variables and inherited data fields.
Basically, what is ruled out by the law of demeter is one object going in and directly manipulating the internal data values of another object.
Instead, all access to data values in another component should be made through procedures - thereby reducing data coupling to the weaker parameter coupling.
Here is another interesting way that object-oriented languages have chosen to differ from each other.
Question: Are sisters and brothers allowed to look at each others private data fields?
An answer of YES is class-level visibility (C++ and Java) and answer of NO is object-level visibiity.
The creation of active values is a good illustration of why parameter coupling is better than direct manipulation.
Suppose we have an existing program and we just want to observe a data value - see when it gets set and changed.
Solution - create a new subclass that just changes those methods that set or read the data value.
@interface Reactor : Object { ... double heat; ... } - (void) setHeat: (double) newValue; - (double) getHeat; @end | @implementation GraphicalReactor : Reactor - (void) setHeat: (double) newValue { /* code necessary to */ /* update gauge */ [ super setHeat: newValue ]; } @end |
Can add new functionality simply by replacing an object with an instance of a subclass; making no change to the original class.
We have several times noted that object have a public and private face - inheritance introduces a third alternative, the subclass face.
There are two types of clients for the class developer. These are user clients (those who use an instance of the class), and subclass clients (those who will subclass from the class).
The public/protected/private modifiers are the primary way to control visibility in most OO languages, the there are other mechanisms as well.
In C++ a friend (class or method) is allowed access to all parts of a class.
class Complex { public: Complex(double, double); friend double abs(Complex&); private: double rp; double ip; }; double abs(Complex& x) { return sqrt(x.rp * x.rp + x.ip * x.ip); }
Friendship is something that is given away, not something that is taken.
Inner classes in Java and, to a lesser extent, nested classes in C++ and C# are allowed to access the data areas in the surrounding class.
class AcontainerClass { ... // return an enumerator public Enumeration elements() { return new MyEnumeration(); } ... // inner class is allowed to see // all aspects of surrounding class private class MyEnumeration implements Enumeration { ... public boolean hasMoreElements () { ... } public Object nextElement() { ... } } }
Uses a lot for event listeners in Java, among other things.
In a public inheritance the public features of a parent become public features of a child.
In a private inheritance, the public and protected features of the parent do not filter through the child; and only the public features of the child are visible.
Names Spaces (in C++), Packages (Java) or Units (Delphi and Object Pascal) give the programmer another way to encapsulate names, and release only those names that are necessary.
package foo; public class bar { // will be visible ... } class baz { // will not be visible outside package ... }
Sometimes want code to depend upon another class.
Sometimes want this even if the dependee doesn't know the dependant. (example, a model and its display).
Can be managed by having a separate ``dependancy manager''. When an object changes, it tells the manager ``notify my depedants''.
In this chapter we have examined a variety of topics related to dependency