Chapter #2 Object-Oriented Modeling — Software Design and Architecture Specialization University of Alberta
This module delves deeper into object-oriented modeling, beginning with an exploration of modeling problems and the evolutionary trajectory of programming languages toward object orientation. It then elucidates the fundamental design principles of abstraction, encapsulation, decomposition, and generalization, highlighting their pivotal role in facilitating problem-solving and fostering the creation of flexible, reusable, and maintainable software. Emphasis is placed on how these principles can be effectively applied in Java code and UML class diagrams to express design structure, focusing on abstraction, encapsulation, and decomposition. Furthermore, the module delves into the intricacies of implementation and interface inheritance within the framework of the generalization design principle.
Creating Models in Design
Effective software development involves thorough comprehension of product requirements and the use of robust design, rather than immediately resorting to coding. Design, positioned between understanding requirements and building the product, continuously addresses both problem and solution spaces. It should facilitate clear communication between users and developers, employing common terms. Numerous strategies and programming languages have emerged to streamline the design process, including the widely adopted object-oriented approach, which enables the representation of concepts as objects in both problem and solution spaces. This approach fosters shared understanding, making it a favored method for tackling complex issues.
Object-oriented design, as explained in Module 1, doesn’t hastily transition from the problem space to the solution space. It comprises conceptual design, which utilizes object-oriented analysis to identify key objects and break down the problem into manageable components. Technical design then refines these objects’ attributes and behaviors, ensuring clarity for developers to implement them as functional software. This iterative process aims to construct and improve software object models, which include entity objects focusing on the problem space, control objects coordinating actions in the solution space, and boundary objects linking external services to the system.
Software models aid in comprehending and organizing the design process, allowing for the application of design principles to simplify and break down complex problems. Continuous evaluation of models ensures adherence to the original problem and essential qualities like reusability, flexibility, and maintainability. These models serve as documentation and are often mapped to skeletal source code, particularly in object-oriented languages like Java. Unified Modelling Language (UML) is a visual notation commonly used for expressing software models, with various diagrams catering to different software aspects. For instance, a structural model describes object behavior and relationships, akin to a scale model in architecture. Understanding the role of models in design and their relation to coding languages leads to a subsequent discussion on the history of programming languages.
Evolution of Programming Languages
Language serves as a means for communication and encompasses various forms such as writing, reading, speaking, drawing, and gestures. Similarly, programming languages have undergone continuous evolution to address new challenges and data structures, leading to shifts in programming paradigms. Understanding the history of programming paradigms is crucial, as older languages and design paradigms may still be relevant, and alternative paradigms might offer more efficient solutions for certain problems. Additionally, new languages may build upon existing structures, making recognition of past developments valuable for current and future programming endeavors.
- COBOL Fortran (1960s)
- Algol 68 Pascal (Early 1970s)
- C Modula-2 (Mid-1970s)
- Object- Oriented Programming (Java, C++, C#, etc.) (1980s to present)
Four Design Principles
The introductory lesson in this module highlights how object-oriented programming enables the construction of object models within a system. To successfully implement such programs, understanding four key design principles is essential: abstraction, encapsulation, decomposition, and generalization.
Abstraction
Abstraction, a key design principle, simplifies complex concepts by focusing on essential aspects while disregarding irrelevant details within a specific context. Following the “rule of least astonishment,” it ensures that essential attributes and behaviors are captured without surprises or definitions beyond its scope, thus maintaining coherence. In programming, constructs like functions, classes, and methods embody this principle. In object-oriented modeling, abstraction is most directly related to the concept of a class, where essential details are defined. Objects created from a class represent instances of a concept, sharing core characteristics while allowing for individual variations, akin to gingerbread men made from a cookie cutter.
Context plays a crucial role in shaping an abstraction, as it can alter the fundamental traits of a concept. For instance, the understanding of a person’s essential characteristics varies depending on the specific context, such as a gaming app or a running exercise app, highlighting the importance of context in defining abstractions. Designers must select the most fitting abstraction considering the software development context, emphasizing the necessity of comprehending the context before abstraction creation. Essential characteristics of an abstraction are comprehensible through fundamental attributes and basic behaviors or responsibilities.
Basic attributes are unchanging characteristics that define a concept, while basic behaviors represent the responsibilities associated with the concept. The context determines the relevance of attributes and behaviors within an abstraction, ensuring that only essential aspects are conveyed. This principle simplifies class designs, making them more concise and comprehensible for others. Considering the dynamic nature of context, it is vital to continuously reassess abstractions, adjusting them as needed based on changes in system purpose or problem requirements.
Encapsulation
Encapsulation, the second major design principle, involves containing elements in a capsule, some accessible from the outside and some not. Three key ideas underlie encapsulation: bundling attributes and behaviors into self-contained objects, exposing specific data and functions via an interface, and restricting access within the object. This principle ensures that class definitions align with the concept’s relevant attributes and behaviors. Encapsulation allows objects of a class to possess distinct data values and exhibit behaviors, simplifying programming by consolidating data and its manipulation in one place. It maintains data integrity and security by restricting access to sensitive information, while enabling flexible implementation changes without affecting the external interface. The concept of a class as a black box, where internal workings are concealed, supports encapsulation, fostering a clear abstraction barrier and enhancing software reusability and modularity.
Decomposition
Decomposition, the third major design principle, involves breaking down a whole entity into distinct parts, making it more manageable to understand and solve. This process also encompasses combining separate parts with diverse functionalities to construct a cohesive unit. Each of these parts represents a separate object that can be created from individual classes in the design, akin to the process of abstraction. Organizing these parts into classes helps maintain their encapsulation and organization. Parts within a whole can have fixed or dynamic quantities, impacting the lifetime of the whole object. Additionally, some parts can function as wholes themselves, comprising further constituent elements. Lifetimes of parts and whole objects may be interdependent or independent, influencing their continued existence. The concept of sharing parts between different wholes is also an essential consideration in the process of decomposition.
Generalization
Generalization, the final principle, minimizes redundancy in problem-solving, commonly employed in various disciplines beyond software development. In coding, methods enable the generalization of behaviors for different data inputs, reducing the need for duplicated code. Object-oriented modeling utilizes generalization through inheritance, where shared attributes and behaviors are extracted into a parent class. Child classes inherit from the parent, allowing them to specialize further. This inheritance facilitates efficient code management and minimizes errors, fostering flexibility, maintainability, and reusability in systems. Generalization ensures changes are easily applied and maintained in the superclass, simplifying software expansion without recreating shared attributes and behaviors. Overall, it leads to more robust solutions and promotes the reuse of code blocks for different classes.
Design Structure in Java and UML Class Diagrams
The design process, outlined in Module 1, involves conceptual design and technical design. Conceptual design, which incorporates prototyping and high-level design simulation, can be effectively visualized using CRC (Class, Responsibility, and Collaboration) cards. CRC cards facilitate clear communication with clients and enable the creation of designs without the complexities of coding. Conversely, for guiding technical design, a more sophisticated technique such as UML (Unified Modeling Language) class diagrams is required. These diagrams offer greater detail compared to CRC cards and facilitate a smoother transition to coding and implementation.
Abstraction
The design principle of abstraction simplifies a concept within a specific context and can be implemented using UML class diagrams at the design level, eventually translating into code. CRC (Class, Responsibility, and Collaboration) cards are used to capture components in systems design, which can be further refined into functions, classes, or collections of other components. In the context of Java, where abstractions are constructed within a class, this lesson primarily focuses on classes.
Encapsulation
The design principle of encapsulation involves bundling data and corresponding functions into a self-contained object, with control over accessibility and restriction. In UML class diagrams, encapsulation is expressed by defining all relevant data within the class attributes and providing specific methods for accessing this data. Visibility is denoted using symbols — and +, where — signifies private accessibility, and + indicates public accessibility. The example of a UML class diagram for a student illustrates how the GPA and degree program attributes are hidden (private) while the operations are public. This ensures that the data remains protected and can only be accessed and modified through designated methods. Encapsulation in UML class diagrams serves as a gate for controlling data, preserving its integrity by using public methods to access private data. Getter methods retrieve data, typically following the format get<Name of the attribute>, while setter methods change data, often following the format set<Name of the attribute>. These methods ensure that data is accessed and modified securely, maintaining data integrity and adhering to approved protocols.
Decomposition
The design principle of decomposition involves the segmentation of a whole entity into distinct parts or the combination of separate parts into a cohesive whole. This process is governed by three types of relationships: association, aggregation, and composition, each defining the interaction between the whole and its constituent parts. These relationships play a crucial and versatile role in software design, ensuring effective structuring and organization of components within a system.
Association
Association signifies a non-dependent relationship between two objects, allowing them to interact temporarily without one object belonging to the other. The relationship does not affect the existence of either object, even if one is destroyed. Additionally, there can be any number of each item in the relationship, with no numerical constraints tied to each other. For instance, a person and a hotel can engage in an association relationship, with a person potentially interacting with multiple hotels and vice versa. The UML representation involves a straight line between the two objects, denoting an association, and the “0…*” notation indicates that each object can be associated with zero or more instances of the other object.
Aggregation
Aggregation represents a “has-a” relationship where a whole entity possesses parts that belong to it, with the potential for parts to be shared among multiple wholes. This relationship is generally considered weak, allowing parts to exist independently. For example, an airliner and its crew demonstrate an aggregation relationship, as the airliner relies on the crew for services, yet both entities can exist independently of each other. In UML class diagrams, aggregation is represented by an empty diamond symbol, with a straight line denoting the relationship between the Airliner object and the Crew Member object. The “0..*” notation signifies that an object can be associated with zero or more instances of the other object. The empty diamond clarifies which object is considered the whole, distinguishing it from the part within the relationship.
Composition
Composition represents a strong “has-a” relationship and is the most interdependent of the decomposition relationships. It signifies an exclusive containment of parts within a whole, wherein the whole cannot exist without its constituent parts. Similarly, if the whole is destroyed, the parts are also eliminated. Access to the parts is typically only available through the whole, emphasizing their exclusive nature. A notable example of a composition relationship is the connection between a house and its rooms, wherein the removal of the house leads to the cessation of the rooms. In UML class diagrams, composition is depicted using a filled-in diamond next to the House object, indicating that the house is the whole within the relationship. The presence of a filled-in diamond signifies a strong “has-a” relationship, with both objects relying on each other for existence. The notation “dot dot star” implies the requirement of one or more Room objects for the House object.
Generalization
The design principle of generalization involves the extraction of shared characteristics from multiple classes into a separate class, facilitating code reuse and the inheritance of these characteristics by subclasses. In UML class diagrams, generalization and inheritance are represented by a solid-lined arrow, where the arrow points upwards, indicating the superclass at the arrow’s head and the subclass at its tail. The structural arrangement of the class diagram follows the convention of placing superclasses at the top and subclasses below them. This representation visually signifies the inheritance relationship between the classes, emphasizing the transmission of shared attributes and behaviors from the superclass to its subclasses.
Interface Inheritance
In some programming languages such as C++, multiple inheritance allows a subclass to have multiple superclasses. In contrast, Java, which has a restriction on single implementation inheritance, introduces interface inheritance as a form of generalization. A class in Java defines a type for its objects, signifying their capabilities through public methods. Subtyping relationships can be expressed between two types, with an instance of a Lion class being both a Lion-type object and an Animal-type object, capable of performing specialized lion behaviors as well as general animal behaviors. This understanding reflects the notion that a lion “is” an animal. In Java, the keyword “extends” is commonly used for implementation inheritance, where a subclass inherits the implementation details of the superclass, enabling it to behave both like its superclass and its own class.
Ibrahim Can Erdoğan