Object-Oriented Design Course Notes PDF
Document Details
Uploaded by Deleted User
University of Alberta
2017
Tags
Summary
These are course notes for an Object-Oriented Design course at the University of Alberta. The notes cover object-oriented thinking, design in the software process, design for quality attributes, and object-oriented modeling. The notes assume a basic understanding of the Java programming language.
Full Transcript
COURSE NOTES Copyright © 2017 University of Alberta. All material in this course, unless otherwise noted, has been developed by and is the property of the University of Alberta. The university has attempted to ensure that all copyright has been obtained. If you believe that something i...
COURSE NOTES Copyright © 2017 University of Alberta. All material in this course, unless otherwise noted, has been developed by and is the property of the University of Alberta. The university has attempted to ensure that all copyright has been obtained. If you believe that something is in error or has been omitted, please contact us. Reproduction of this material in whole or in part is acceptable, provided all University of Alberta logos and brand markings remain as they appear in the original work. Version 0.1.0 Object -Oriented Design | 2 TABLE OF CONTENTS Course Overview 4 Module 1: Object-oriented analysis and design 5 Object-Oriented Thinking 5 Design in the Software Process 7 Requirements 8 Design 9 Compromise in Requirements and Design 12 Design for Quality Attributes 13 Trade-offs 13 Context and Consequences 14 Satisfying Qualities 14 Compromise 16 Class Responsibility Collaborator 17 CRC Cards 17 Prototyping and Simulation 18 Module 2: Object-Oriented Modelling 20 Creating Models in Design 21 Evolution of Programming Languages 23 Four Design Principles 29 Abstraction 29 Encapsulation 31 Decomposition 33 Generalization 35 Design Structure in Java and UML Class Diagrams 36 Abstraction 37 Encapsulation 39 Decomposition 41 Generalization 46 Module 3: Design Principles 57 Evaluating Design Complexity 57 Coupling 58 Cohesion 59 Separation of Concerns 59 Information Hiding 65 Conceptual Integrity 68 Generalization Principles 70 Specialized UML class diagrams 73 UML Sequence Diagrams 73 UML State Diagrams 77 Model Checking 79 Course Resources 81 Course References 81 Glossary 83 Object -Oriented Design | 3 COURSE OVERVIEW This course examines object-oriented design as part of the foundation for becoming an experienced software architect. The material starts with an introduction to object-oriented thinking, examining the role of design and quality attributes in the software development process. This will also include an overview of the Class Responsibility Collaborator cards technique. The course then transitions into object-oriented analysis and design. Building on the material presented in the first module, you will learn about object-oriented modelling, particularly how design principles are communicated and expressed in Java and Unified Modelling Language (UML). The last section of this course will focus on the more complex aspects of design principles that support object-oriented design, which must be understood to create flexible, reusable, and maintainable software. Some more specialized UML diagrams are also explained. This course assumes basic understanding of the programming language Java. Upon completion of this course, you will be able to: 1) Explain object-oriented analysis and design. 2) Engage in object-oriented modelling. 3) Explain design principles for object-oriented programming. Object -Oriented Design | 4 MODULE 1: OBJECT-ORIENTED ANALYSIS AND DESIGN Upon completion of this module, you will be able to: (a) Explain object-oriented thinking. (b) Understand the role of design and communication in the software process as well as the link between these concepts and the use of diagrams. (c) Design for quality attributes. (d) Model Class Responsibility Collaborator (CRC) cards. Object-Oriented Thinking Object-oriented modelling is a major topic in this specialization. Before we can discuss this topic in depth, it is important to learn how to think about problems and concepts as object oriented. You probably associate the term “object-oriented” with coding and software development. While that is true, the notion of being object-oriented can apply outside of the role of a developer. Object-oriented thinking involves examining the problems or concepts at hand, breaking them down into component parts, and thinking of those as objects. For example, a tweet on Twitter or a product on an online shopping website could be considered objects. When translated to object-oriented modelling, object-oriented thinking involves representing key concepts through objects in your software. Note that concepts are broad in nature. Even instances of people, places, or things can be distinct objects in software. Objects may have specific details associated with them, which are relevant to users. For example, a person object may have details such as name, age, gender, and occupation. A place object may have a size, or a name. An inanimate object may have dimensions or a colour. Object-Oriented Design | 5 Objects might also have behaviours or responsibilities associated with them. For example, a person may have associated behaviours such as sitting down or typing. An electronic device may be responsible to power on or off, or to display an image. By using objects to represent things in your code, the code stays organized, flexible, and reusable. Objects keep code organized by putting related details and specific functions in distinct, easy-to-find places. In the above examples, the details of the objects stay associated with the objects themselves. Objects keep code flexible, so details can be easily changed in a modular way within the object, without affecting the rest of the code. In the above example of a person object, a person’s details such as occupation may change, and not affect the rest of the code. Objects allow code to be reused, as they reduce the amount of code that needs to be created, and keeps programs simple. Objects are self-aware in software production, even if they are inanimate objects. For example, a mobile phone “knows” its specifications. Similarly, in object-oriented modelling, an object such as a chair would know its dimensions and location. In object-oriented thinking, often everything is considered an object, even if animate or live. And objects are all self-aware, even if inanimate. Object-Oriented Design | 6 It is good practice to prepare for object-oriented design by accustoming yourself to thinking about the world around you in terms of objects, and the attributes those objects might have. DID YOU KNOW? A good exercise to help you start object-oriented thinking is to look at the room around you and identify what might be objects. For example, you might see a computer, a person, or some kind of furniture. These all have their own specific details and may have behaviours or responsibilities that would be relevant to a user of that object. For a computer system, details might include operating software or display resolution. Responsibilities might include turning on and off, or displaying a screen. Even the room itself is an object! It may have a seating capacity, a room number, or a purpose that provide specific details and responsibilities to the room as an object. What objects are around you? What kinds of details and behaviours might they have? Design in the Software Process When software is developed, it generally goes through a process. In simple terms, a process takes a problem and creates a solution that involves software. A process is generally iterative. These iterations consist of taking a set of requirements based on the identified problem(s) and using them to create conceptual design mock-ups and technical design diagrams, which can then be used to create a working software implementation, which must also pass testing. This process is repeated for each set of requirements, eventually creating a complete solution for the project. Many projects fail when this process is skipped over, especially when work immediately begins with coding, and there is a lack of understanding of the requirements and design. Object-Oriented Design | 7 It is important to allot time to form the requirements and design, even if they are not perfectly established. The work of coding relies on certain assumptions, and it can be difficult to change those assumptions once coding has begun. Requirements and design activities help you to understand what assumptions you need so that you create the right product. DID YOU KNOW? In a survey from The Standish Group, 13% of respondents noted incomplete requirements impaired their projects! Diving straight into implementation work is a leading cause of project failure. Let us briefly examine the steps of requirements and design activities in the software process. These steps will require you to think like an architect, so you will need to consider the structure and behaviour of your software. By the end of this lesson you will understand that design work involves outlining a solution and it may include evaluating different alternatives. Requirements Requirements are conditions or capabilities that must be implemented in a product, based on client or user request. They are the starting point of a project—you must understand what your client wants. However, in order to elicit requirements, it is important to ask for more than simply the client’s vision. Instead, eliciting requirements involves actively probing the client vision, clarifying what may not have been told, and asking questions about issues the client may not have even considered. This allows you to understand the full scope of what you to build and what your client wants in a product before you actually start coding. In addition to establishing specific needs for the project, it is also important to establish potential trade-offs the client may need to make in the solution. For example, a client may decide to sacrifice a feature in order to ensure that a program runs faster, if speed is an important need. Object-Oriented Design | 8 Once requirements and trade-offs are established, they may serve as the foundation for design. To better understand requirements, imagine you are an architect building a house. Requirements allow you to understand what a homeowner wants in a house before you start building. The homeowner may tell you what rooms they want, but you may need to ask follow-up questions about what rooms may be missing from their list, what size the house and rooms might be, any constraints on the house based on restrictions, how clients want rooms to be placed, or what direction the house should face. These help you better understand what you will be building. Design When the initial set of requirements has been created, the next step in the process is to produce a conceptual design and a technical design. This results in the creation of two different kinds of artifacts: conceptual mock-ups and technical diagrams. Conceptual Design Conceptual designs are created with an initial set of requirements as a basis. The conceptual design recognizes appropriate components, connections, and responsibilities of the software product. However, more specific technical details are deferred until the technical design. Conceptual designs outline the more high- level concepts of your final software product. Conceptual designs are expressed or communicated through conceptual mock-ups. These are visual notations that provide initial thoughts for how requirements will be satisfied. Mock-ups for software involving user interfaces are often presented as wireframes, which are a kind of blueprint or basic visual representation of the product. See the example below of a wireframe mock-up for a web page. Whether for user interfaces or for the software product itself, conceptual mock-ups can be hand- drawn sketches or drawings made using computer tools. Conceptual mock-ups help to clarify design decisions with clients and users by providing a simple way to illustrate and discuss how a product will work. Object-Oriented Design | 9 Mock-ups illustrate major components and connections, or relations between the components. Once you start to create a mock-up, you may more easily see what components are missing or may not work. These flaws would require further clarification with your client or involve additional conceptual design work. Every component also has a task it needs to perform. This is known as its responsibility. Mock-ups do not outline technical details, because that falls outside the scope of conceptual design. For example, let us return to the metaphor of building a house. The components for the architectural example of building a house might be: the lot the house will be built on, the house, and rooms inside the house. Connections might be how rooms are accessible to each other. The house has the responsibility of providing enough power, water, and support for all the components within it. Rooms in the house, such as the kitchen, may also have responsibilities, such as providing space for storing kitchenware, appliances, food supplies, plus power and water for meal preparation. However, specifics about wiring and plumbing are not mentioned in the conceptual design. These technical details cannot be fully addressed until the conceptual mock-ups are completely understood. For example, the size of the electrical distribution panel for the house will require adding up the power requirements of each of the rooms. Object-Oriented Design | 10 Best practice is to form the conceptual design before moving on to the technical design. The clearer the conceptual design, the better the technical design, and the more likely your software will be built right. Technical Design Technical designs build on conceptual designs and requirements to define the technical details of the solution. In the conceptual design, the major components and connections as well as their associated responsibilities of the software being developed are outlined. The technical design brings this information to the next stage—it aims to describe how these responsibilities are met. The technical design is not finished until each component has been refined to be specific enough to be designed in detail. In order to accomplish this, technical designs begin by splitting components into smaller and smaller components that are specific enough to be designed in detail. By breaking down components more and more into further components, each with specific responsibilities, you get down to a level where you can do a detailed design of a particular component. The final result is that each component will have their technical details specified. In order to communicate technical design, technical diagrams are used. Technical diagrams visualize how to address specific issues for each component, as conceptual mock-ups are generally not specific enough to capture this information. There are many different technical diagrams that can be used to describe the structure and behaviour of components, which will be addressed later on in this specialization. Technical diagrams therefore help co- ordinate development work. To continue with the architectural example used throughout this lesson, imagine having to design a kitchen. A kitchen is a component of a house on its own, but it will require further smaller components, such as flooring. The technical design may indicate that the flooring will need to be made of a material that is easy to clean, particularly if the client plans on doing a lot of cooking— cooking can be a messy business! Object-Oriented Design | 11 Compromise in Requirements and Design When in the design phase, there may need to be compromises in creating an acceptable solution. Constant communication and feedback is key to creating the right solution that meets client needs and works within any restrictions that may exist. Drawing on the architectural example used throughout this lesson, imagine the client would like an open kitchen in their house that has no obstructions between it and the dining room. But what if a post and beam is needed in that area to support the second floor of the house? The homeowner and the project will need to work out a compromise in that situation. Designs will need to be reworked if components, connections, and the responsibilities of the conceptual design prove impossible to achieve in the technical design, or if they fail to meet requirements. It is important to continually check with clients that conceptual mock-ups capture what they want. It is easier to re-design in the planning stages, than once coding has started. Larger systems generally require more design time. There are more components, connections, and responsibilities to keep track of in larger systems. And as these components themselves are large, they may need to be refined down to smaller components before their design can be detailed. Once a feasible design has been agreed upon, the technical diagrams become the basis for constructing the intended solution. Components at this stage may be refined enough to become collections of functions, classes, or other components. These pieces become a more manageable problem that developers can individually implement. There are many design techniques that may be used to get the most out of the design process. The rest of this specialization will examine those techniques. Object-Oriented Design | 12 Design for Quality Attributes When developing software, it is important to take a broad view on how to achieve the desired requirements. This lesson examines how competing ideals, roles and perspectives, potential trade-offs, and project realities need to be taken into account and balanced in software design. Trade-offs This course has reviewed the importance of requirements and design in creating software. Sometimes, there are restrictions on design that require compromise. Besides software requirements based on desired functionality, there are also quality attributes to define how well this functionality must work. But your decisions may also involve trade-offs in different quality attributes, such as performance, convenience, and security, and these attributes need to be balanced. For example, it is important to consider how quality attributes can compete in a proposed solution under different situations. Then, taking this into account and weighing it against the requirements of the product, a suitable compromise can be determined. This balancing act is an ongoing constant for software architecture. Software architects must find the best balance between quality attributes—often by evaluating which one is more important. Deadlines can also influence what is feasible to do within a certain time frame. Let’s consider, for example, designing a front door to a house. Security is a quality attribute that might be important, but if you add too many locks to the door, it may be difficult to open easily and will become inconvenient to use. A good design should balance security with convenience and performance. Object-Oriented Design | 13 Context and Consequences Context provides important information when deciding on the balance of qualities in design. For example, software that stores personal information, which the public can access, may have different security requirements than software that is only used by corporate employees. In order to establish context, it is important to talk to stakeholders. Software design also must consider the consequences. Sometimes, choices made in software design have unintended consequences. For example, an idea that seems to work fine for a small amount of data may be impractical for large amounts of data. A good practice is to seek other perspectives on technical designs for a more well-rounded implementation. This can be done by asking other developers for their opinion, or by having a design review session. It is also good practice to test a system carefully before fully implementing a system. During the design process, you might consider prototyping alternative ideas and running tests to see what works best. Tests can help catch unintended consequences. For example, testing with both small and large amounts of data in the above example might reveal the system limitations. Satisfying Qualities Qualities are achieved through satisfying functional and non- functional requirements, which in turn are the basis for the design process. Functional requirements describe what the system or application is expected to do. A key quality to achieve by satisfying a functional requirement is that of correctness. For example, if you are designing a music app, the app must be able to download and play a song. The design needs to be able to outline a solution that correctly meets this requirement. Object-Oriented Design | 14 Non-functional requirements specify how well the system or application does what it does. Non-functional requirements to satisfy might include performance, resource usage, and efficiency; these requirements can be measured from the running software. For example, the music app may have non-functional requirements to download music only to a certain memory limit. Other qualities that software often satisfies in non-functional requirements include reusability, flexibility, and maintainability. This helps inform how well the code of software can evolve and allow for future changes. Requirements are often incomplete at first, but are resolved with further interactions with clients and end users. Instructor’s Note: If you are interested in learning more about functional and non-functional requirements, see the requirements course of the Coursera specialization offered by the University of Alberta on Software Product Management. Functional and non-functional requirements are important to satisfy, but there may be important constraints and limitations that will lead to compromises. For this reason, it is important to communicate and determine what is acceptable to stakeholders. Consider this example: all cars meet the functional requirement of providing transportation; however, non-functional requirements and the emphasis on certain qualities can vastly change the final product— different accelerations, handling, weight, and fuel economy can make the difference between a minivan and a sports car. Reviews and tests should also be used to verify that required qualities on design and software implementation are satisfied. Some qualities may also be validated with feedback from end users. Instructor’s Note: If you are interested in learning more about reviews, see the reviews course of the Coursera specialization offered by the University of Alberta on Software Product Management. Object-Oriented Design | 15 Compromise In addition to balancing qualities and meeting functional requirements when designing software, it is important to consider multiple perspectives. Software must satisfy qualities that matter to users as well as developers. In other words, how the software structure is organized may affect the quality of performance, as understood by users, and the qualities of reusability and maintainability, as understood by developers. Below are some common trade-offs in qualities for software design: Performance and maintainability – High performance code may be less clear and less modular, making it harder to maintain. Alternately, extra code for backward compatibility may affect both performance and maintainability. Performance and security – Extra overhead for high security may lessen performance. Balance between qualities must be understood and taken into account during design. It is important to prioritize and understand what qualities are needed. A good question to ask to help you determine what compromises can be made is: Is there a way to cut back on a certain quality to balance another? DID YOU KNOW? Some common qualities to take into account in software design include: performance, maintainability, security, and backwards compatibility. It is also important to consider the constraints by project realities on your project. To develop the product, qualities must be balanced with resources available such as cost, time, and manpower. Object-Oriented Design | 16 Class Responsibility Collaborator So far, this module has reviewed the process of eliciting requirements and using conceptual design to gather initial thoughts on how to satisfy those requirements in software development. Components, connections, and responsibilities for some requirements are established during this stage. This module has also examined how components and connections are refined through the technical design process, and by taking quality attributes into account, in order to establish technical details. This allows components and connections to be more easily implemented. This next lesson presents an important technique to help represent the components, responsibilities, and connections at a high level when forming the conceptual design. This technique is the use of Class, Responsibility, Collaborator (CRC) cards. CRC cards help record and organize components into classes, identify component responsibilities, and determine how they collaborate with each other. Therefore, they also help refine the components in your software design. CRC Cards During the process of conceptual design, it is helpful not only to identify components, responsibilities, and connections but also to represent them. One technique is to use Class, Responsibility, Collaborator (CRC) cards. CRC cards are used to record, organize, and refine the components of system design. They can be compared to note cards, which are used to organize talking points. CRC cards are designed with three sections: the class name at the top of the card, the responsibilities of the class on the left side of the card, and the collaborators on the right side of the card. See the image below for an example of what a CRC card might look like, about the size of a physical index card. Object-Oriented Design | 17 Class Name Responsibilities Collaborators To keep track of each candidate component and its responsibilities using a CRC card, you place a component’s name in the class name section, and the responsibilities in the responsibilities section. Connections are captured in the collaborators section. Connections or collaborators indicate other classes that the class at the top of the card interacts with to fulfill its responsibilities. These steps are repeated iteratively and new cards are created until all the classes, responsibilities, and collaborators are identified for a system. In system design, CRC cards has a purpose—it forces designers to keep breaking components down into smaller components and classes that can be individually described on a card. Prototyping and Simulation The use of CRC cards is a simple system that has many advantages. They are cheap, editable, and widely available. They help sort information into manageable pieces. A key advantage of using CRC cards is that they allow you to physically reorganize your design. As each of the components are represented by a card, you can move related cards together, or situate cards to suggest relationships. This allows you to theoretically explore how your system will work and to identify any shortcomings in the design. You can also experiment with moving these cards around in new orders and analyzing the resulting consequences, allowing you to play with alterative designs. This means that CRC cards can be used to prototype and simulate a system for conceptual design. Object-Oriented Design | 18 When you develop designs, these are sometimes referred to as CRC models. CRC cards should be organized by placing closely collaborating components together. This makes it easier to understand the relationships or connections between classes or components. CRC cards are excellent tools to bring to software development team meetings. All the cards can be placed on the table, and facilitate a discussion or a simulation with the team of how these classes work together with other classes to achieve their responsibilities. This allows you to both visually explain your system and gain potential input from other parties. CRC cards are useful tools, but they are most powerful when used for prototyping and simulation for conceptual design. Many other techniques have been developed to help you design more effectively. The rest of this specialization will focus on some of these various design techniques. Object-Oriented Design | 19 MODULE 2: OBJECT-ORIENTED MODELLING Upon completion of this module, you will be able to: (a) Describe issues in creating models for design. (b) Understand how programming languages evolved toward object orientation. (c) Explain the four major design principles used in object-oriented modelling: a. Abstraction b. Encapsulation c. Decomposition d. Generalization (d) Express the above design principles in using UML class diagrams and Java code. (e) Explain and express implementation inheritance. (f) Explain and express interface inheritance. The previous module in this course provided an introduction to the importance of design in the software development process, and ended with explaining the advantage of using CRC cards to complete a conceptual design. This module will explore object-oriented modelling even further. It will begin by examining modelling problems and how programming languages evolved towards object orientation. Then, the four major design principles of abstraction, encapsulation, decomposition, and generalization will be discussed. These principles help in problem solving and lead to developing software that is flexible, reusable, and maintainable. They are key principles to follow for developing a good design for your software. This module will also explore how to express design structure in Java code and UML class diagrams using the principles of abstraction, encapsulation, and decomposition. Finally, it will discuss implementation and interface inheritance within the design principle of generalization. Object-Oriented Design | 20 Creating Models in Design It is important when working on a software development project not to jump right into creating code to solve the problem. Instead, making the right product involves understanding the full requirements of your product and using good design. The design step falls between understanding your requirements and building the product. It iteratively deals with both the problem space and the solution space. The design should also present and describe concepts in a way that users and developers both understand, so they may discuss using common terms. Design is such an important step in software development, and there have been many approaches developed over time to help make this process easier. For example, some design strategies and programming languages have been created for specific kinds of problems. One approach to help make the design process easier is the object- oriented approach. This allows for the description of concepts in the problem and solution spaces as objects—objects are a notion that can be understood by both users and developers, because object-oriented thinking applies to many fields. This shared knowledge makes it possible for users and developers to discuss elements of complex problems. Object-oriented programming with object-oriented languages is therefore a popular means for solving complex problems. A good design does not just jump from a concept within the problem space to dealing with it in the solution space. Object- oriented design is no exception. As reviewed in Module 1, object- oriented design consists of: Conceptual design uses object-oriented analysis to identify the key objects in the problem and breaks down the problem into manageable pieces. Technical design uses object-oriented design to further refine the details of the objects, including their attributes and behaviours, so it is clear enough for developers to implement as working software. Object-Oriented Design | 21 These design activities happen iteratively and continuously. The goal during software design is to construct and refine “models” of all the objects of the software. Categories of objects involve: entity objects, where initial focus during the design is placed in the problem space control objects that receive events and co-ordinate actions as the process moves to the solution space boundary objects that connect outside services to your system, as the process moves towards the solution space Software models help you understand and organize the design process for the objects. Design principles and guidelines are applied to complex problems: to simplify objects in the model and break them down into smaller parts and to look for commonalities that can be handled consistently. Models should be continuously critiqued and evaluated to ensure the original problem is addressed and qualities such as reusability, flexibility, and maintainability are satisfied. Models also serve as design documentation for your software. In fact, models are often mapped to skeletal source code, particularly for an object-oriented language like Java. Software models are often expressed in a visual notation, called Unified Modelling Language (UML). Object-oriented modelling has different kinds of models or UML diagrams that can be used to focus on different software issues. For example, a structural model might be used to describe what objects do and how they relate. This is analogous to a scale model of a building, which is used in architecture. Now that you have an understanding of the roles models play in design and of the relationship between models and coding languages, the next lesson will turn to reviewing the history of programming languages. Object-Oriented Design | 22 Evolution of Programming Languages Language is the word that we use to describe a system for communicating thoughts and ideas with each other. Writing, reading, speaking, drawing pictures, and making gestures are all part of language! Languages must be continually evolving in order to stay “alive” and be used by people. Programming languages are no exception to this, and just like traditional languages, they have evolved over time. Often, programming languages evolved to provide solutions or more effective solutions to needs or problems that the current programming languages cannot meet. New languages or ideas may also arise to address new data structures. The ideas used in computer languages caused shifts in programming paradigms. DID YOU KNOW? An example of design strategies and programming languages suited for specific kinds of problems that you may be familiar with is top-down programming. Top-down programming is generally used to solve data- processing problems. This design strategy consists of mapping processes in the problem to routines to be called, beginning with the “top” process. Generally, this design is expressed through a tree of routines. These routines would be implemented in a programming language that supported subroutines. It is important to know the history of programming paradigms. As a software developer, you may still encounter systems that use older languages and design paradigms. As well, although object-oriented programming is a powerful tool, there may be problems that are best or more efficiently solved with another paradigm. Finally, it is important to understand this history, as new languages may not force new structures but only modify existing ones. Some old ways of doing something or old paradigms may be expanded on so much that the new structures may be difficult to recognize. Knowledge of the past may help. Object-Oriented Design | 23 Below is a table summarizing major programming paradigms in the history of programming languages. Programming Time Solutions afforded Unresolved issues Language period by Programming Language of Programming Language COBOL and Fortran followed an imperative paradigm that broke up large If changes are made to the data, COBOL 1960s programs into smaller programs called subroutines. then subroutines might run into Fortran cases where the global data is not As computer processing time was costly, it was important to maximize what was expected. Better data processing performance. To solve this problem, global data was used so management is needed to avoid that data was located all in one place in the computer’s memory and these problems. accessible anywhere for a program. This meant that subroutines only had to go to one place to access variables. Subprogram B Subprogram Subprogram A C Global Data Object-Oriented Design | 24 Programming Time Solutions afforded Unresolved issues Language period by Programming Language of Programming Language Algol 68 Early In the 1960s, global data was used. However, any changes to the data may Towards the mid-1970s, computer result in issues for the subroutines. processing time became less Pascal 1970s expensive. At the same time, human The solution introduced the idea of scopes and local variables – subroutines labour was more expensive and or procedures could each have their own variables. became the more time-consuming factor in software development. The advances in computer processing Procedure allowed more complex problems to Procedure C B be asked of computers. But it also Nested Procedure meant that software was quickly Procedure Local Data growing, and having one file to A maintain programs was difficult to maintain. Global Data These languages supported the use of abstract data type, which is defined by the programmer and not built into the language. This is a grouping of related information that is denoted with a type. This allows information to be organized in a meaningful way. By having data bundled and passed into different procedures through the use of data types, this means that a procedure can be the only one that modifies a piece of data. There no longer needs to be a worry that data will be altered by another procedure. Object-Oriented Design | 25 Programming Time Solutions afforded Unresolved issues Language period by Programming Language of Programming Language C Mid- By the mid-1970s computers were faster and able to tackle more complex It is not easy for an abstract data problems. However, this meant that the programs of the past were quickly type to inherit from another in these Modula-2 1970s becoming too big to maintain. This led to new languages, that provided a languages. This means that means to organize programs into separate files, and allow developers to although as many data types as more easily create multiple, but unique, copies of abstract data types. wanted can be created, one type cannot be declared an extension of another. Data Data Procedure Procedure A B Procedure C Modules For example, in the programming language C, each file contained all the associated data and functions that manipulated it, and declared what could be accessed through a separate file called a header file. Object-Oriented Design | 26 Programming Time Solutions afforded Unresolved issues Language period by Programming Language of Programming Language Object- 1980s Although programs were become easier to manage through abstract data Object oriented programming is the types, there was still no way to for data types to inherit from each other. The predominant programming Oriented to concepts of object-oriented design became popular during this time period paradigm now. Programming present as a solution to these problems. (Java, C++, C#, etc.) Object-oriented design seeks to: make an abstract data type easier to write structure a system around abstract data types called classes introduce the ability for an abstract data type to extend another through a concept known as inheritance Data Data Procedure Procedure A B Procedure C Classes Object-Oriented Design | 27 Programming Time Solutions afforded Unresolved issues Language period by Programming Language of Programming Language Under this paradigm, software systems can be built of entirely abstract data types. This allows the system to mimic the structure of the problem—in other words, the system can represent real-world objects or ideas more accurately. Class definition files in object-oriented programming replace the files in C and Modula-2. Each class defines a type with associated data and functions. These functions are also known as methods. A class acts like a factory, making individual objects all of a specific type. This allows data to be compartmentalized and manipulated into its own separate classes. Object-Oriented Design | 28 Four Design Principles As described in the first lesson of this module, object-oriented programming allows you to create models of how objects are represented in your system. However, to create an object-oriented program, you must examine the major design principles of such programs. Four of these major principles are: abstraction, encapsulation, decomposition, and generalization. Abstraction Abstraction is one of the four major design principles that will be examined in this lesson. Abstraction is one of the main ways that humans deal with complexity. It is the idea of simplifying a concept in the problem domain. Abstraction breaks a concept down into a simplified description that ignores unimportant details and emphasizes the essentials needed for the concept, within some context. An abstraction should follow the rule of least astonishment. This rule suggests that essential attributes and behaviours should be captured with no surprises and no definitions that fall beyond its scope. This prevents irrelevant characteristics from becoming part of an abstraction and helps to ensure that the abstraction makes sense for the concept’s purpose. Program constructs includes elements such as functions, classes, enumerations, and methods. In object-oriented modelling, abstraction pertains most directly to the notion of a class. When abstraction is used to determine the essential details for some concept, those details may be defined in a class. Any object created from a class has the essential details to represent an instance of some concept, but it may have some individual characteristics as well. Think of a cookie cutter used to create gingerbread men. Each instance of a cut cookie belongs to the class of “gingerbread men” and share essential characteristics such as head, arms, and legs, even if they are decorated differently. Object-Oriented Design | 29 Context or a specific perspective is critical when forming an abstraction. This is because context might change the essential characteristics of a concept. For example, consider the essential characteristics of the concept of a person. This can be hard to understand without context, as this concept is vague and the person’s purpose is unknown. But, in a gaming app, the essential characteristics of a person would be in the context of a gamer. In a running exercise app on the other hand, the essential characteristics of a person would be in the context of an athlete. It is up to the designer to choose the abstraction that is most appropriate to the context of the software development, and the context must be understood before creating an abstraction. The essential characteristics of an abstraction can be understood in two ways: through basic attributes and through basic behaviours or responsibilities. Basic attributes are characteristics that do not disappear over time. Although their values may change, the attributes themselves do not. For example, the concept of a lion may have an age attribute. That value may change, but the lion always has an age attribute. In addition to basic attributes, an abstraction describes a concept’s basic behaviours. A lion may have behaviours such as hunting, eating, and sleeping. These are also responsibilities that the lion abstraction does for its purpose of living. An abstraction, as explained above, should only convey a concept’s essential attributes and behaviours. Context helps determine what is relevant. For example, when considering the lion in a hunting setting, it is irrelevant to consider what position the lion prefers to sleep in. If context changes, the right abstraction may change as well. Object-Oriented Design | 30 There are many benefits from the principle of abstraction. It helps to simplify class designs, so they are more focused, succinct, and understandable to someone else viewing them. As abstractions rely strongly on context or perspective, it is important to carefully consider what is relevant. Likewise, if the purpose of the system being built, or if the problem being solved changes, it is important to re-examine your abstractions and change them accordingly. Encapsulation Encapsulation is the second major design principle that will be examined in this lesson. This principle involves a concept that allows something to be contained in a capsule, some of which you can access from the outside and some of which you cannot. There are three ideas behind encapsulation. These are: The ability to “bundle” attribute values (or data) and behaviours (or functions) that manipulate those values, into a self-contained object. The ability to “expose” certain data and functions of that object, which can be accessed from other objects, usually through an interface. The ability to “restrict” access to certain data and functions to only within the object. “Bundling” occurs naturally when a class is defined for a type of object. The principle of abstraction helps determine what attributes and behaviours are relevant about a concept in a determined context. The principle of encapsulation takes this a step further and ensures that these characteristics are bundled together in the same class. Encapsulation therefore allows distinct objects created from a particular class to have their own data values for the attributes and exhibit resulting behaviours. This makes programming much easier, as the data and the code that manipulate that data are located in the same place. Object-Oriented Design | 31 An object’s data should only contain what is relevant for that object. For example, a lion object “knows” what food it hunts but does not know what animals live in a different continent, because that is not relevant data. A class therefore only knows what attributes or data is relevant to it. A class also defines behaviours through methods. Methods manipulate the attribute values or data in the object to achieve the actual behaviours. Certain “methods” can be exposed or made accessible to objects of other classes. This provides an interface to other objects to use the class. Integrity and Security As one of the ideas of encapsulation is restricting access to certain data and functions to only within an object, this naturally links encapsulation to data integrity and the security of sensitive information. If certain attributes and methods are restricted from outside access, except through specific methods, then the data cannot be changed through variable assignments. This prevents assumptions or dependencies from breaking for the data within an object. Likewise, restriction of access helps keep sensitive information from being revealed, even from queries that rely on sensitive data to provide answers. Changeable Implementation Encapsulation is also a useful principle for implementing software changes. As the ability to “expose” data is separate from the “bundle” of attributes itself, this means that the implementation of attributes and methods can change, but the accessible interface of a class can remain the same. Users accessing or querying the class do not need to worry about how the implementation works behind the interface—they will still use the same means to access information. Object-Oriented Design | 32 Black box The idea in encapsulation that supports the line of thinking in changeable implementation is also tied to a concept known as black box thinking. The computation steps taken within a class never need to be known by any other class, as long as they are able to access the interface. A class is therefore like a black box that you cannot see into for details about how attributes are represented or how methods compute their results. What happens in the “box” to achieve an expected behaviour doesn’t matter, as long as it is possible to provide inputs and obtain outputs by calling methods. Encapsulation achieves an abstraction barrier through black box thinking where the internal workings of a class are not relevant to the outside world. This results in an abstraction that reduces complexity for users of the class. Encapsulation also increases reusability because of black box thinking. Another class only needs to know the right method to call to get a desired behaviour, what arguments to supply as inputs, and what appears as outputs or effects. In other words, encapsulation keeps software modular and easy to work with. Classes are easy to manage, as their internal behaviour is not relevant to other classes, as long as they can interface together. Decomposition Decomposition is the third major design principles that will be examined in this lesson. It consists of taking a whole thing, and dividing it into different parts. Alternately, decomposition can also indicate taking separate parts with different functionalities and combining them to create a whole. Decomposition allows problems to broken into smaller pieces that are easier to understand and solve. Object-Oriented Design | 33 The general rule for decomposition is to look at the different responsibilities of a whole and evaluate how the whole can be separated into parts that each have a specific responsibility. Each of these parts are in fact separate objects that can be created from separate classes in your design. In this way, decomposition is similar to abstraction where you are dividing a whole into objects with essential characteristics. Each different kind of part within a whole can prescribe a class, so we can keep our parts better organized and encapsulated on their own. The class for the whole object then relates to the classes for the constituent part objects. The Nature of Parts A whole might have a fixed or dynamic number of a certain type of part. If there is a fixed number, then over the lifetime of the whole object, it will have exactly that much of the part object. Think of an oven with four burners. The number of burners is fixed for the oven object. Some parts, on the other hand, may have a dynamic number. This means the whole object may gain new instances of those part objects over its lifetime. Think of items of food within a refrigerator object – these might change from day to day. Note that a part can also serve as a whole, which is made up of further constituent parts. For example, a kitchen is a part of a house. But the kitchen may be made up of further parts, such as an oven and a refrigerator. Another issue worth noting in decomposition is that whole objects and part objects have lifetimes. Sometimes, these lifetimes are closely related, and the part shares the same lifetime as the whole— one cannot exist without the other. If the temperature cooling gauge for a fridge dies, then the fridge will cease to work. But sometimes, the part and the whole can exist independently and have different lifetimes. For example, if an item of food goes bad in the fridge, the fridge will still continue its function. Whole things may also contain parts that are shared with another whole at the same time. However, sometimes sharing a part is not possible or intended. Object-Oriented Design | 34 Generalization Generalization is the final one of the four major design principles that will be examined in this lesson. Generalization helps reduce redundancy when solving problems. It is a common principle used in many disciplines outside of software development. In coding, algorithmic behaviours are often modelled through methods. A method allows a programmer to generalize a behaviour, so the behavior can be applied to different input data. This generality reduces the need to have identical code throughout a program. In object-oriented modelling, generalization is a main design principle, but beyond creating a method that can be applied to different data, object-oriented modelling achieves generalization by classes through inheritance. In generalization we take repeated, common, or shared characteristics between two or more classes and factor them out into another class. This allows you to have two kinds of classes: a parent class, and a child class. Child classes inherit attributes and behaviours of parent classes. This means that repeated, common, or shared characteristics go into parent classes. Parent classes capture general ideas and generally have broader application. It is possible for multiple child classes to inherit from a single parent class. Those multiple child classes will all receive these common attributes and behaviours, although it is likely that each child class will have additional attributes and behaviours that allow them to be more specialized in what they can do. In standard terminology, a parent class is known as a superclass and a child class is called a subclass. From the above explanation, we can understand that inheritance allows a superclass to form a generalization and for its subclasses to be more specialized. Parent classes save time and prevent errors, particularly when they are used for multiple child classes. Without parent classes, systems are not flexible, maintainable, or reusable. Object-Oriented Design | 35 Instructor’s Note: A good tip to remember for naming superclasses and subclasses – although classes can be named after anything you want, it is good practice to name them after things you are trying to model. This makes code easier to understand! Generalization presents many advantages to object-oriented modelling. Since subclasses inherit attributes and behaviours from superclasses, this means that any changes to the code that is common to both subclasses need only be made once in the superclass. In other words, changes to software are easier to apply and maintain. Another advantage is that subclasses can be easily added without having to recreate all the common attributes and behaviours for them, so software is easier to expand. Generalization provides more robust software solutions and allows for more reusable code because the same blocks of code can be used for different classes. DID YOU KNOW? Both methods and inheritance exemplify the generalization design principle through the D.R.Y. or “Don’t Repeat Yourself” rule. Methods and inheritance allow developers to reuse code, resulting in less code and repetition overall. Design Structure in Java and UML Class Diagrams The design process, as explained in Module 1 of this course, consists of both the conceptual design and the technical design. Conceptual design, including prototyping and simulating higher- level designs, can be visualized through CRC cards. CRC cards make it easy to communicate with client, and they allow you to create designs without the distraction of code. However, to guide technical design, a more sophisticated technique that can communicate your needs clearly to developers is needed. One technique used for technical design is that of UML class diagram, also known as simply a class diagram. These class diagrams provide more detail than CRC cards and allow for easier conversion to classes for coding and implementation. Object-Oriented Design | 36 This lesson will look at how the design principles of abstraction, encapsulation, decomposition, and generalization work with Java and UML class diagrams. Abstraction The design principle of abstraction allows for the simplification of a concept to its essentials within some context. Abstraction can be applied at the design level using UML class diagrams. The design is eventually turned into code. CRC cards capture components in systems design. Components can eventually be refined into functions, classes, or collections of other components. As this course uses Java, where abstractions are formed in a class, this lesson will focus on classes. Let us look at how a CRC card might translate into a class diagram. Below is an example of a CRC card, as abstracted for a food item in the context of a grocery store. Food Know grocery ID Know name Know manufacturer Know expiry date Know price Check if on sale Here is the same concept as a class diagram. Food groceryID: String name: String manufacturer: String expiryDate: Date price: double isOnSale() : boolean Object-Oriented Design | 37 Every concept or class in a class diagram is represented with a box, as above. You will notice three sections in the box. Class Name Properties Operations The class name is the same as the class name in your Java class. The properties section is equivalent to Java’s member variables. This section defines the attributes of the abstraction by using a standard template for variable name and variable type. Variable types can be classes or primitive types. : The operations section is equivalent to Java’s methods. This section defines the behaviours of the abstraction, using a standard template for the operation name, parameter list, and return type. ( ) : In the example above, food objects have a method to return if it is on sale or not. This method has been named “isOnSale”. The method will return a boolean to represent if it is on sale. A Boolean value is either true or false. The isOnSale operation takes no parameter, so no parameter list is included. If you were to add a parameter to the operation, such as a date in this case, the parameter would follow the same template as the class diagram’s properties. The final section would read: isOnSale(date: Date) : Boolean Object-Oriented Design | 38 You will notice that class diagrams distinguish a difference between responsibilities that become properties and responsibilities that become operations, whereas CRC cards list them together. Helping distinguish this ambiguity makes class diagrams easier to translate into code for a programmer. Drawing on our food example, we can see how easy it is to turn a class diagram into a class in Java. public class Food { public String groceryID; public String name; public String manufacturer; public Date expiryDate; public double price; public boolean isOnSale( Date date ) { } } The class name in the class diagram turns into a class in Java. The properties turn into member variables. Operations become methods. It is possible to use this mapping in reverse to turn code into class diagrams. Instructor’s Note: The above example has everything public, meaning the member variables and methods can be accessed from any other code besides this class. This assumption applies for now. In later lessons, you will learn about access modifiers in Java for more controlled access. Encapsulation The design principle of encapsulation involves three ideas: Data and functions that manipulate that data are “bundled” into a self-contained object. Data and functions of the object can be exposed or made accessible from other objects. Data and functions of the object can be restricted to only within the object. Object-Oriented Design | 39 In a UML class diagram, encapsulation is expressed by having all of the object’s relevant data defined in attributes of the class, and by providing specific methods to access those attributes. UML class diagrams can express encapsulation. The class diagram itself already bundles data and functions in a self-contained object. However, access and restriction (two aspects of visibility) can be represented as well, through the use of symbols – and +. Below is an example of a UML class diagram for a student. Student -gpa: float -degreeProgram: String +getGPA(): float +setGPA( float ) +getDegreeProgram(): String +setDegreeProgram( String ) In this example, the attributes gpa and degreeProgram are hidden from public accessibility, as indicated by the minus sign (-). In other words, the minus sign indicates that a method or attribute is private and can only be accessed from within the class. On the other hand, the operations are public, as indicated by the plus sign (+). In other words, the plus sign indicates that a method can be accessed publicly. In this case, the public methods can be used to manipulate the student’s GPA. This prevents the student’s GPA attribute from being directly manipulated, and it controls the data is accessed and changed. Encapsulation in UML class diagrams helps you determine the “gate” to controlling data, by using only public methods to access the data attributes of the class. For every piece of essential data, the use of public methods to access private data creates protection from unexpected direct change of that data. This preserves the data integrity. Object-Oriented Design | 40 There are two different kinds of methods typically used to preserve data integrity. These are: Getter methods, which are used to retrieve data. These methods typically have the format: get, where the attribute is the value that will be returned through the method. Getters often retrieve private data. Setter methods, which are used to change data. These methods typically have the format: set, where the attribute is what will be changed through the method. Setters often set a private attribute in a safe way. These kinds of methods help ensure that data is accessed in an approved way. Decomposition The design principle of decomposition takes a whole thing and divides it into different parts. It also does the reverse, and takes separate parts with different functionalities, and combines them to form a whole. There are three types of relationships in decomposition, which define the interaction between the whole and the parts: Association aggregation composition All three are useful and versatile for software design. Let us examine each of these relationships. Object-Oriented Design | 41 Association Association indicates a loose relationship between two objects, which may interact with each other for some time. They are not dependent on each other—if one object is destroyed, the other can continue to exist, and there can be any number of each item in the relationship. One object does not belong to another, and they may have numbers that are not tied to each other. An example of an association relationship could be a person and a hotel. A person might interact with a hotel but not own one. A hotel may interact with many people. Association is represented in UML diagrams as below: Person 0..* 0..* Hotel The straight line between the two UML objects denote that there is a relationship between the two UML objects of person and hotel, and that relationship is an association. The “zero dot dot star” (0…*) on the right side of the line shows that a Person object is associated with zero or more Hotel objects, while the “zero dot dot star” on the left side of the line shows that a Hotel object is associated with zero or more Person objects. Association can be represented in Java code as well. public class Student { public void play( Sport sport ){ execute.play( sport ); } … } In this code excerpt, a student is passed a sport object to play, but the student does not possess the sport. It only interacts with it to play. The two objects are completely separate, but have a loose relationship. Any number of sports can be played by a student and any number of students can play a sport. Object-Oriented Design | 42 Aggregation Aggregation is a “has-a” relationship where a whole has parts that belong to it. Parts may be shared among wholes in this relationship. Aggregation relationships are typically weak, however. This means that although parts can belong to wholes, they can also exist independently. An example of an aggregate relationship is that of an airliner and its crew. The airliner would not be able to offer services without the crew. However, the airliner does not cease to exist if the crew leave. The crew also do not cease to exist if they are not in the airliner. Aggregation can be represented in UML class diagrams with the symbol of an empty diamond as below: Airliner 0..* 0..* Crew Member In this diagram, a straight line is used again to symbolize a relationship between the Airliner object, and the Crew Member object. “Zero dot dot stars” (0..*) are used to show again that an object might have a relationship with zero or more of the other object. In this case, the “zero dot dot star” on the right side of the line indicates that a Airliner object might have zero or more crew members. The “zero dot dot star” on the left side of the line indicates that a Crew Member object can be had by zero or more Airliner objects. The empty diamond indicates which object is considered the whole and not the part in the relationship. Object-Oriented Design | 43 Aggregation can be represented in Java code as well. public class Airliner { private ArrayList crew; public Airliner() { crew = new ArrayList(); } public void add( CrewMember crewMember ) { … } } In the Airliner class, there is a list of crew members. The list of crew members is initialized to be empty and a public method allows new crew members to be added. An airliner has a crew. This means that an airliner can have zero or more crew members. Object-Oriented Design | 44 Composition Composition is one of the most dependent of the decomposition relationships. This relationship is an exclusive containment of parts, otherwise known as a strong “has-a” relationship. In other words, a whole cannot exist without its parts, and if the whole is destroyed, then the parts are destroyed too. In this relationship, you can typically only access the parts through its whole. Contained parts are exclusive to the whole. An example of a composition relationship is between a house and a room. A house is made up of multiple rooms, but if you remove the house, the room no longer exists. Composition can be represented with a filled-in diamond using UML class diagrams, as below: House 1..* Room The lines between the House object and the Room object indicates a relationship between the two. The filled-in diamond next to the House object means that the house is the whole in the relationship. If the diamond is filled-in, it symbolizes that the “has-a” relationship is strong. The two objects would cease to exist without each other. The one “dot dot star” indicates that there must be one or more Room objects for the House object. Composition can be represented using Java code as well. public class House { private Room room; public House(){ room = new Room(); } } Object-Oriented Design | 45 In this example, a Room object is created at the same time that the House object is, by instantiating the Room class. This Room object does not need to be created elsewhere, and it does not need to be passed in when creating the House object. The two parts are tightly dependent with one not being able to exist without the other. Generalization The design principle of generalization takes repeated, common, or shared characteristics between two or more classes and factors them out into another class, so that code can be reused, and the characteristics can be inherited by subclasses. Generalization and inheritance can be represented UML class diagrams using a solid-lined arrow as shown below: Animal Lion The solid-lined arrow indicates that two classes are connected by inheritance. The superclass is at the head of the arrow, while the subclass is at the tail. It is conventional to have the arrow pointing upwards. The class diagram is structured so that superclasses are always on top and subclasses are towards the bottom. Object-Oriented Design | 46 Inherited superclass’ attributes and behaviours do not need to be rewritten in the subclass. Instead, the arrow symbolizes that the subclass will have the superclass’ attributes and methods. Superclasses are the generalized classes, and the subclasses are the specialized classes. It is possible to translate UML class diagrams into code. Let us build on the example above. Animal #numberOfLegs: int #numberOfTails: int #name: String +walk() +run() +eat() Lion + Roar() In this diagram, the Lion class is the subclass and the Animal class is the superclass. The subclass inherits all the attributes and behaviours from the superclass. As explained above, it is therefore unnecessary to put all of the superclass’ attributes and behaviours in the subclass of the UML class diagram. You may also notice the use of a # symbol. This symbolizes that the Animal’s attributes are protected. Protected attributes in Java can only be accessed by: the encapsulated class itself all subclasses all classes within the same package Object-Oriented Design | 47 In Java, a package is a way to organize classes into a namespace that represents those classes. The UML class diagrams can be translated into code. public abstract class Animal { protected int numberOfLegs; protected int numberOfTails; protected String name; public Animal( String petName, int legs, int tails ) { this.name = petName; this.numberOfLegs = legs; this.numberOfTails = tails; } public void walk() { … } public void run() { … } public void eat() { … } } Since the Animal class is a generalization, it should not be created as an object on its own. The keyword abstract indicates that the class cannot be instantiated. In other words, an Animal object cannot be created. The Animal class is a superclass, and any class that inherits from the Animal class will have its attributes and behaviours. Those subclasses that inherit from the superclass will share the same attributes and behaviours from the Animal class. Here is the code for creating a Lion subclass: public class Lion extends Animal { public Lion( String name, int legs, int tails ) { super( name, legs, tails ); } public void roar() { … } } None of the attributes and behaviours inherited from the Animal class need to be declared. This mirrors the UML class diagram, as Object-Oriented Design | 48 only specialized attributes and methods are declared in the superclass and subclass. Remember, the UML class diagram represents our design. As inherited attributes and behaviours do not need to be re-stated in the code, they do not need to be re- stated in the subclass in the diagram. Inheritance is declared in Java using the keyword extends. Objects are instantiated from a class by using constructors. With inheritance, if you want an instance of a subclass, you must give the superclass a chance to prepare the attributes for the object appropriately. Classes can have implicit constructors or explicit constructors. Below is an example of an implicit constructor: public abstract class Animal { protected int numberOfLegs; public void walk() { … } } In this implementation, we have not written our own constructor. All attributes are assigned zero or null when using the default constructor. Below is an example of an explicit constructor: public abstract class Animal { protected int numberOfLegs; public Animal( int legs ) { this.numberOfLegs = legs; } } In this implementation, an explicit constructor will let us instantiate an animal with as many legs we want. Explicit constructors allow you to assign values to attributes during instantiation. public abstract class Animal { protected int numberOfLegs; public Animal( int legs ) { this.numberOfLegs = legs; } } Object-Oriented Design | 49 public class Lion extends Animal { public Lion( int legs ) { super( legs ); } } A subclass’ constructor must class its superclass’ constructor if the superclass has an explicit constructor. Explicit constructors of the superclass must be referenced by the subclass; otherwise, the superclass attributes would not be appropriately initialized. To access the superclass’ attributes, methods, and constructors, the subclass uses the keyword super. Subclasses can override the methods of its superclass, meaning that a subclass can provide its own implementation for an inherited superclass’ method. public abstract class Animal { protected int numberOfLegs; public void walk() { System.out.println( "Animal is walking" ); } } public class Lion extends Animal { public void walk() { System.out.println( "I'd rather nap" ); } } In the above example, the Lion class has overridden the Animal class’s walk method. If asked to walk, the system would tell us that a Lion object would rather nap. Types of Inheritance Java is capable of supporting several different types of inheritance. The above examples in the lesson are implementation inheritance. Object-Oriented Design | 50 In Java, only single implementation inheritance is allowed. This means that while a superclass can have multiple subclasses, a subclass can only inherit from a single superclass. For example, the Animal class might be a superclass to multiple subclasses: a Lion class, a Wolf class, or a Deer class. Each of these classes might have specialized behaviours or characteristics, so a Lion object knows how to roar but might not know how to howl, like a Wolf object. Subclasses can also be a superclass to another class. Inheritance can trickle down through as many classes as desired. Inheritance allows the generalization of related classes into a single superclass, and it still allows the subclasses to retain the same set of attributes and behaviours. This removes redundancy in the code and makes it easier to implement changes. Interface Inheritance Other languages like C++ support multiple inheritance. This is when a subclass can have two or more superclasses. Java addresses the restriction of single implementation inheritance by offering interface inheritance, another form of generalization. To understand that, first some programming language and design notions need to be explained. A class denotes a type for its objects. The type signifies what these objects can do through public methods. In modelling a problem, it may be necessary to express subtyping relationships between two types. For example, instances of a Lion class are Lion-classed objects, which may perform specialized lion behaviours. A Lion type may also be a subtype of an Animal type. So, a Lion object is of a Lion type and an Animal type—a Lion object behaves like a lion and like an animal. Therefore, a lion “is” an animal. In Java, implementation inheritance with the keyword extends is often used for subtyping. So, if a subclass extends a superclass, it will behave like the superclass and like its own class. The subclass inherits the “implementation details” of the superclass. Object-Oriented Design | 51 A Java interface also denotes a type, but an interface only declares method signatures, with no constructors, attributes, or method bodies. It specifies the expected behaviours in the method signatures, but it does not provide any implementation details. A Java interface may also be used for subtyping. If a class implements an interface, then the class not only behaves like itself, it is also expected to behave according to the method signatures listed in the interface. The class needs to provide the method body details for what it means to implement the interface. An interface is like a contract to be fulfilled by implementing classes. In implementation inheritance, there is consistency between the superclass type and the subclass type. A subclass object is usable anywhere in your program where you are dealing with the superclass type. Similarly, in interface inheritance, there is consistency between the interface type and the implementing class type. In Java, the keyword interface is used to indicate that one is being defined. The letter “l” is sometimes placed before an actual name to indicate an interface. public interface IAnimal { public void move(); public void speak(); public void eat(); } The interface shows that an animal has the behaviours of moving, speaking, and eating, but these behaviours are not implemented here, and there is no description as to how these behaviours are performed. The interface also does not encapsulate any attributes of the superclass—this is because attributes are not behaviours. Instructor’s Note: The latest Java allows default implementations of the methods in interfaces. There are resources in the Course Readings section of the notes if you wanted to read more about this change! Object-Oriented Design | 52 In order to use an interface, you must declare that you are going to fulfill the contract as described in the interface. The keyword in Java for this action is implements. public class Lion implements IAnimal { public void move() { … } public void speak() { … } public void eat() { … } } In this example, the Lion class has declared that it will implement or describe the behaviours that are in the interface—in this case, move, speak, and eat methods. You must have all the method signatures explicitly declared and implemented in the class. «interface» Interface Name A Java interface can describe the expected common behaviour of multiple classes, without directly implementing that behavior. An interface can be implemented by multiple classes, with each implementing class defining their own, appropriate version of the behaviour. Also, a class may implement multiple different interfaces. Interfaces can be drawn in a similar way to classes in UML diagrams. Interfaces are explicitly noted using guillemets, or French quotes, to surround the word «interface». The interaction between an interface and a class that is implementing the interface is indicated using a dotted arrow. The implementing class touches the tail end of the arrow and the interface touches the head of the arrow. The conventional way to draw interfaces on your UML class diagrams is to have the arrow pointing upward, so the interface is always on the top, and the classes that implement them are towards the bottom. Object-Oriented Design | 53 There are several advantages to interfaces. Understanding what these are will help you determine if you should use interfaces or inheritance when designing a system. Like abstract classes, which are classes that cannot be instantiated, interfaces are a means in which you can achieve polymorphism. In object-oriented languages, polymorphism is when two classes have the same description of a behaviour, but the implementations of that behaviour may be different. An example of this might be how animals “speak.” A lion may roar, but a wolf howls. Both animals can speak, but the behaviour implementation is different. This can be demonstrated through code, as below: public class Lion implements IAnimal { public void speak() { System.out.println( "Roar!" ); } } public class Wolf implements IAnimal { public void speak() { System.out.println( "Howl!" ); } } Object-Oriented Design | 54 Interfaces can inherit from other interfaces, but interfaces should not be extended if you are simply trying to create a larger interface. Interface A should only inherit from interface B if the behaviours in interface A can fully be used as a substitution for interface B. Examine the example below to better understand this concept. public interface IVehicleMovement { public void moveOnX(); public void moveOnY(); } In this example, a vehicle can only travel either along the x-axis or the y-axis. But what if we want a different type of vehicle like a plane or a submarine to be able to move along a third axis as well? In order to avoid adding an extra behaviour to the interface, so as not to affect vehicles who can only move along two axes, we create a second interface that inherits from the first. public interface IVehicleMovement3D extends IVehicleMovement { public void moveOnZ(); } Another advantage of interfaces relates back to multiple implementation inheritance. This is because inheriting from two or more superclasses can cause data ambiguity—if a subclass inherits from two or more superclasses that have attributes with the same name or behaviours with the same method signature, then it is not possible to distinguish between them. As Java cannot tell which one is referenced, so it does not allow for multiple inheritance to prevent data ambiguity. Interfaces, however, do not have this issue. In Java, a class can implement as many interfaces as desired. This is because interfaces are only contracts and they do not enforce a specific way to complete these contracts, so overlapping method signatures are not a problem. A single implementation for multiple interfaces with overlapping contracts is acceptable. There is no ambiguity, as a class may only have one definition of a specific method, and it is the same implementation no matter which interface. Java avoids data ambiguity in this way. Object-Oriented Design | 55 Classes can implement one or more interfaces at a time, allowing for multiple types. Interfaces enable you to describe behaviours without the need to implement them, which allows reuse of those abstractions. Interfaces allow the creation of programs with reusable and flexible code. It is important to remember, however, that you should not generalize all behavioural contracts into interfaces. Interfaces fulfill a specific need: to provide for a way for related classes to work with consistency. Now that you have an introduction to design principles and their manifestation in technical diagrams, this course will now turn to examining more nuanced understandings of those same design principles. Object-Oriented Design | 56 MODULE 3: DESIGN PRINCIPLES Upon completion of this module, you will be able to: (a) Understand the general guidelines for evaluating the structure of your software solution so that it’s flexible, reusable, and maintainable. These guidelines are: a. evaluating design complexity with coupling and cohesion b. the separation of concerns c. information hiding d. conceptual integrity e. generalization principles (b) Model behaviours of the objects in your software using the specialized UML state and UML sequence diagrams. (c) Explain the importance of model checking. This module covers general guidelines for evaluating the structure of software solutions. These guidelines help ensure that software is flexible, reusable, and maintainable. It will also cover modelling behaviours of the objects in your software using the UML state and UML sequence diagrams. Evaluating Design Complexity It is important to keep modules simple when you are programming. If your design complexity exceeds what developers can mentally handle, then bugs will occur more often. To help control this, there must be a way of evaluating your design complexity. Design complexity applies to both classes and the methods within them. This lesson will use the term module to refer to program units containing classes and the methods within them. Object-Oriented Design | 57 A system is a combination of various modules. If the system has a bad design, then modules can only connect to other specific modules and nothing else. A good design allows any modules to connect together without much trouble. In other words, in a good design, modules are compatible with one another and can therefore be easily connected and re-used. The metrics often used to evaluate design complexity are coupling and cohesion. Coupling Coupling focuses on complexity between a module and other modules. Coupling can be balanced between two extremes: tight coupling and loose coupling. If a module is too reliant on other modules, then it is “tightly coupled” to others. This is a bad design. However, if a module finds it easy to connect to other modules through well-defined interfaces, it is “loosely coupled” to others. This is good design. In order to evaluate the coupling of a module, the metrics to consider are: degree, ease, and flexibility. Degree is the number of connections between the module and others. The degree should be small for coupling. For example, a module should connect to others through only a few parameters or narrow interfaces. This would be a small degree, and coupling would be loose. Ease is how obvious are the connections between the module and others. Connections should be easy to make without needing to understand the implementations of other modules, for coupling purposes. Flexibility indicates how interchangeable the other modules are for this module. Other modules should be easily replaceable for something better in the future, for coupling purposes. Object-Oriented Design | 58 Signs that a system is tightly coupled and has a bad design are: a module connects to other modules through a great number of parameters or interfaces corresponding modules to a module are difficult to find a module can only be connected to specific other modules and cannot be interchanged Cohesion Cohesion focuses on complexity within a module, and represents the clarity of the responsibilities of a module. Like complexity, cohesion can work between two extremes: high cohesion and low cohesion. A module that performs one task and nothing else, or that has a clear purpose, has high cohesion. A good design has high cohesion. On the other hand, if a module encapsulates more than one purpose, if an encapsulation has to be broken to understand a method, or if the module has an unclear purpose, it has low cohesion. A bad design has low cohesion. If a module has more than one responsibility, it is a good idea to split the module. It is important to balance between low coupling and high cohesion in system design. Both are necessary for a good design. However, in complex systems, complexity can be distributed between the modules or within the modules. For example, as modules are simplified to achieve high cohesion, they may need to depend more on other modules, thus increasing coupling. On the other hand, as connections between modules are simplified to achieve low coupling, the modules may need to take on more responsibilities, thus lowering cohesion. Separation of Concerns One of the design principles examined in the previous module was that of decomposition. Decomposition divides a whole into different parts. To understand why decomposition is necessary in design, the principle of separation of concerns must be examined. Object-Oriented Design | 59 A concern is a very general notion: it is anything that matters in providing a solution to a problem. Separation of concerns is about keeping the different concerns in your design separate. When software is designed, different concerns should be addressed in different portions of the software. Consider a software system that solves a problem. That problem could either be simple, with a small number of subproblems, or complex, with a large number of subproblems. Concepts can be abstracted from the problem space. When these abstractions are implemented in the software, it can lead to more concerns. For example, some of these concerns might involve what information the implementation represents, what it manipulates, and what gets presented at the end. In order not to become lost in the resulting concerns and subproblems, the design must be organized so all concerns are carefully considered and addressed. To do this, different subproblems and concerns are separated into different sections during design and construction of the software system. This applies the principle of separation of concerns. Separation of concerns provides many advantages. They allow you to develop and update sections of the software independently. Using separation of concerns also means that you do not need to know how all sections of code work in order to update a section. Finally, separation of concerns allows changes to be made to one component without requiring a change in another. Separation of concerns is a key idea that underlies object-oriented modelling and programming. When addressing concerns separately, more cohesive classes are created and the design principles of abstraction, encapsulation, decomposition, and generalization are enforced: Abstraction occurs as each concept in the problem space is separated with its own relevant attributes and behaviours. Encapsulation occurs as the attributes and behaviours are gathered together into their own section of c