class Polygon implements Shape { Point getVertex(index i) {...} void draw() {...} String toString() {...} } class Triangle extends Polygon { double getArea() {...} } abstract class NinetyDegreeParallelogram extends Polygon { double getArea() {...} }class NinetyDegreeParallelogram extends Polygon { double getArea() {...} } class Square extends NinetyDegreeParallelogram {...} class Rectangle extends NinetyDegreeParallelogram {...} abstract class ClosedCurve implements Shape {...}class ClosedCurve implements Shape {...} class Circle extends ClosedCurve { double getRadius() {...} Point getCenter() {...} double getArea() {...} void draw() {...} String toString() {...} } class Ellipse extends ClosedCurve { double getApogeeRadius() {...} double getPerigeeRadius() {...} Point getFocus1() {...} Point getFocus2() {...} Point getCenter() {...} double getArea() {...} void draw() {...} String toString() {...} } 2 A complete version of the example is available at http://www.aspectprogramming.com/papers/aosd2007/ Listing 1 Each Shape can return its area, draw itself, and return a string representation. The objects are read-only at this point. Each shape is either a Polygon or a ClosedCurve. Concrete Polygons include Squares, Rectangles, and Triangles, while Circles and Ellipses are concrete ClosedCurves. Some methods are implemented in abstract helper classes, while others are implemented in the concrete classes, as indicated. Note that Squares and Circles are not subclasses of Rectangles and Ellipses, respectively. This is discussed later in the Liskov Substitution Principle section. For a simple example like this, a pure object-oriented design would be adequate in most practical cases. However, the AOP techniques discussed will be most valuable in larger design problems with long-term maintenance, reuse, and enhancement needs. Also, while the example is for Java and AspectJ, the principles should be valid for most AOP systems. 2.1 The OOD Design Principles 2.1.1 The Single Responsibility Principle (SRP) A class or aspect should have only one reason to change. A class that mixes multiple concerns, each of which is an axis of potential change, effectively couples the concerns. If the class needs to evolve along one concern axis, the changes often compromise the class’s ability to support the other concerns, even when they remain fixed. Changing one concern also imposes accidental changes on clients of the class which don't depend on that concern. Hence, it is difficult to modify the class, making it rigid and reuse is compromised in applications where a dependent is forced to accept changes in features that it doesn’t need. Note that the definition emphasizes change; a tangled module that never needs to change poses no practical problems. The SRP is the OOD solution to the classic “separation-ofconcerns” problem. The SRP splits orthogonal state and behavior into separate classes, but it usually isn’t sufficient when a crosscutting concern interacts with other concerns in fine-grained ways. The shapes example exhibits a common SRP problem, while drawing shapes and converting to string formats is useful, it is incidental to the “true nature” of shapes. Hence, it is cross-cutting, especially since the details of these operations can vary depending on the context. String representations could be in XML or another format, for example. Drawing depends on the graphics libraries in use. As shown, using a typical non-aspect approach, one variant of each concern is implemented in an invasive way. An alternative approach would be to use a design pattern like Visitor [14]. I will demonstrate an AOP alternative shortly. 2.1.2 The Open-Closed Principle (OCP) Software entities (classes, aspects, modules, functions, etc.) should be open for extension, but closed for modification. If a change in one location causes a cascade of changes to other points in the system, those cascades result in brittle systems, because it is hard to find all those points where changes are required. This situation is another form of rigidity. The OCP is a design strategy that minimizes this problem. An entity should be closed for modification, meaning its code cannot be changed, yet open for extension, through subclassing or composition. The OCP reduces rigidity and brittleness because preventing change in the original entity reduces a cascade of changes in dependents. An example OCP violation is conditional logic that switches on the known classes in a hierarchy (or a “type code”), where a unique action is taken for each case. Introduction of a new class forces updates to all such code blocks. Instead, overloaded methods should be added to the class hierarchy that implement the variant behaviors (e.g., draw()in the example). The conditional logic collapses to a single polymorphic method invocation. However, what if the behavior is actually crosscutting and doesn't really belong in the hierarchy? A related technique that supports the OCP is the Template Method pattern [14], where a base class implements a concrete method that defines a protocol and which calls one or more abstract methods to complete the details. Subclasses implement the abstract methods to fill in the appropriate behaviors. However, as discussed in [13], the OCP still has one limitation; it is not possible to anticipate all changes that clients might want. A new client requirement might not be satisfied by the existing abstraction. This will force the abstraction to change, which will probably cause a cascade of client changes. Even if we could anticipate all possible changes, it would not be desirable to design the original module for all such contingencies, as this could lead to SRP violations, overly-complicated interfaces, bloated and inefficient code, and increased implementation effort, all to support options for change that might never be used. Let us return to the example and use aspects to refactor it in ways that better support both the SRP and the OCP. As it stands now, the Shape hierarchy satisfies the OCP because we can easily add new shapes without modifying any existing code. Still, let us refactor the design to extract the crosscutting toString() and draw() “features”. For brevity, unchanged classes are omitted. package shapes; interface Shape { // draw() removed double getArea(); } abstract class Polygon implements Shape { Point getVertex(index i) {...} }class Polygon implements Shape { Point getVertex(index i) {...} } class Circle extends ClosedCurve { double getRadius() {...} Point getCenter() {...} double getArea() {...} } class Circle extends ClosedCurve { double getApogeeRadius() {...} double getPerigeeRadius() {...} Point getFocus1() {...} Point getFocus2() {...} Point getCenter() {...} 3 The exceptions are the places where decisions are made about which shapes to instantiate, e.g., Factories [14]. double getArea() {...} } <X>ToString.aj files: // separate aspect files package shapes.tostring; // for all “toString()”... aspect PolygonToString { String Polygon.toString() { StringBuffer buff = new StringBuffer(); buff.append(getClass().getName()); ... append name and area fields ... ... append each line, as “from” and “to” points return buff.toString(); } } aspect CircleToString { String Circle.toString() {...} } aspect EllipseToString { String Ellipse.toString() {...} }
[1]
David H. Lorenz,et al.
Cona: aspects for contracts and contracts for aspects
,
2004,
OOPSLA '04.
[2]
Lodewijk Bergmans,et al.
Composing crosscutting concerns using composition filters
,
2001,
CACM.
[3]
Daniel P. Friedman,et al.
Aspect-Oriented Programming is Quantification and Obliviousness
,
2000
.
[4]
William G. Griswold,et al.
The structure and value of modularity in software design
,
2001,
ESEC/FSE-9.
[5]
Kris De Volder.
Aspect-Oriented Logic Meta Programming
,
1998,
ECOOP Workshops.
[6]
Harold Ossher,et al.
Multi-Dimensional Separation of Concerns and the Hyperspace Approach
,
2002
.
[7]
Karl G. Scheidt,et al.
Join Point Encapsulation
,
2002
.
[8]
G GriswoldWilliam,et al.
Modular Software Design with Crosscutting Interfaces
,
2006
.
[9]
Ralph Johnson,et al.
design patterns elements of reusable object oriented software
,
2019
.
[10]
Carlos José Pereira de Lucena,et al.
Modularizing design patterns with aspects: a quantitative study
,
2005,
AOSD '05.
[11]
Martin E. Nordberg.
Aspect-Oriented Dependency Inversion
,
2001
.
[12]
Robert C. Martin.
Agile Software Development, Principles, Patterns, and Practices
,
2002
.
[13]
G. Kiczales,et al.
Aspect-oriented programming and modular reasoning
,
2005,
Proceedings. 27th International Conference on Software Engineering, 2005. ICSE 2005..
[14]
Gregor Kiczales,et al.
Design pattern implementation in Java and aspectJ
,
2002,
OOPSLA '02.
[15]
Kris Gybels,et al.
On the Existence of the AOSD-Evolution Paradox
,
2003
.
[16]
Sushil Krishna Bajracharya,et al.
An analysis of modularity in aspect oriented design
,
2005,
AOSD '05.