Вы находитесь на странице: 1из 20

Chapter 6 - Inheritance and Composition

Inheritance is one of the four key concepts of object orientation. It allows one or more
classes to inherit the attributes and methods of another class, referred to as the parent or
superclass. The inheriting subclasses or child classes automatically have all of the
attributes and methods of their parent class, and they can have their own unique attributes
and methods in addition to those. The advantages of code reuse and code maintenance
make inheritance a really powerful tool if developers can recognize when to use it.

Identifying inheritance situations

The best way to identify if two or more classes constitutes inheritance is when it
describes an “Is A”, “Is A Kind Of”, or “Is A Type Of” relationship.

51
Example: Given we have classes named CargoShuttle, StarFighter, Spaceship. These
three classes constitute inheritance relationships because CargoShuttle IS A Spaceship,
likewise StarFighter IS A Spaceship.

Since cargo shuttle is a spaceship, and starfighter is a spaceship, then that’s a clue that
there are some shared behaviors on those two. Putting those two classes together next to
each other, it is easy to see that they have several attributes and methods in common.

52
Stripping out those common elements from those two classes and putting them into a
superclass called Spaceship, that StarFighter and CargoShuttle sub classes will inherit
from. That’s indicated in UML diagrams with an open arrow that looks like a wedge. The
StarFigther’s fireMissiles() and CargoShuttle’s method to drop cargo remain in their
respective subclasses, because those behaviors are unique to each one.

53
From here, defining more types of Spaceships, like a WarpCruiser, that can automatically
inherit all of the elements of the Spaceship superclass. Now there may be times when
some of those inherited methods don’t quite work for a specific subclass. Maybe the
WarpCruiser moves differently than a generic Spaceship, because it travels at warp speed,
and many languages will allow a subclass to replace the implementation of an inherited
method through a process called overriding . So the WarpCruiser class could override just
the move() method to use its own unique version of it.

Inheritance, on the other hand, can also extend multiple


levels deep.

If there are going to be other types of vehicles in our


game, then creating a higher level of Vehicle class for
Spaceship to inherit from is a good idea, since after all, a
Spaceship is a Vehicle.

A word of warning - it’s common for new object-oriented


developers to overemphasize inheritance and to come up
with class diagrams with five levels of inheritance for
everything. Don’t go looking for inheritance, because

54
inheritance usually announces itself. Finding yourself creating one or two class diagrams
without any inheritance is just perfectly fine.
Using inheritance

As seen above, the basic idea of inheritance is really just a minor syntax change between
languages. In Java, it uses the keyword extend after the child class name and indicates
the parent class from which the former will inherit from.

When it comes to the details of overriding, the details are a bit too specific to each
language to be useful here. For details of overriding, refer to the documentation of your
chosen programming language.

55
When a child class needs to call a method originated from the parent class, the above
photo shows a perfect example to implement it in a specific programming language. In
Java, for the CargoShuttle child class to call the setShield() method from its
parent Spaceship class, it uses the dot notation method super.[parent
method] format, i.e. super.setShield() .

Abstract and concrete class


Abstract classes are classes that provide a generic concept of an object that can be used to
generate child classes. Abstract classes, however, cannot be instantiated but can be a
parent.

All abstract classes should have at least one abstract method which is a method that’s
declared with a method signature but not actually implemented in the abstract class. That
method is passed to any subclasses and they are responsible for actually implementing it.
However, abstract classes, alongside an abstract method, can have methods that have
actual implementation, meaning, containing a code block.

One scenario to implement an abstract class is when a parent class will not be used to
instantiate any objects at all.

56
In UML diagrams, abstract classes and abstract methods are identified by an italicized
class and method name.

Concrete class, on the other hand, are classes that are meant to be instantiated and cannot
be extended or inherited from, and implements any missing functionality from the
abstract class.

57
Take note that not all languages have official keywords to identify abstract and concrete
classes. Implementing abstract classes in other languages is simply by omission, just
don’t create an object from it. In Java, the keyword abstract indicates that a class or
method is an abstract class or method, and the keyword final indicates that a class is a
concrete class. In fact, writing classes without formally marking it as abstract or final is
fine. The benefit of including keywords like abstract and final is to communicate
intentions for a class to other programmers. It lets them know whether or not a class was
designed with inheritance or not.

Interfaces

Interfaces are another common form of abstraction that's supported by many object-
oriented languages. An interface is a programming structure that declares a set of
methods for a class to implement, but the interface itself doesn't contain any functionality.
There's no implemented code or behavior. It's just a collection of method signatures to
specify a service.

58
The image above is how interfaces are implemented in Java. Instead of using the keyword
class , it uses the interface keyword. To use the interface on a class, it uses the
keyword implements after the class name followed by the interface name. The class
implementing the interface will be the one to provide the behavior of the methods
indicated in the interface. It is like signing a contract, promising that the new class will
implement all the methods in that interface. The developers have the freedom to
implement the inner workings of those methods in any way they want as long as the
method names, inputs, and outputs match the interface.
Given the example in the photo, the interface Moveable has a method signature move(int
x, int y) with void return type. This provides a capability to any class that will implement
this interface to have a move() behavior with them.

Going back to the conceptual object model that was created, some classes share the same
behaviors - Asteroid, Spaceship, and Missile shares the move behavior, and Spaceship
and Missile share the spawn behavior. Though Asteroid, Spaceship, and Missile are
different object models and cannot inherit the move behavior from each other, creating an
interface containing the method signature of move behavior is more likely the best option

59
here, since all of these object models will move within the same game space area.
Likewise, the Spaceship and Missile cannot inherit the spawn behavior, or the ability to
create itself and appear within the game space, from each other, creating another interface
containing the method signature for spawn behavior is an option.

From the example above, some might say that interfaces are just an extreme version of
abstract classes where none of the methods are implemented and everything’s abstract.
They can seem similar at first, but they serve different purposes.
Interfaces are used to represent a capability that a class implements. Whereas an abstract
class represents a type that another class can inherit from. While most languages only
allow a class to inherit from one other class, interfaces don’t have that restriction. A class
can implement multiple interfaces.

60
I

nterfaces are represented in UML using a box that looks similar to class, but include a tag
with double angle quotes to indicate that it’s an interface. Classes that implement an
interface use the same arrow as inheritance, but with a dashed line instead of a solid line
to indicate that it’s implementing an interface rather than inheriting from a class.

Advantage of using interfaces in a project is when, for instance, you want to change the
direction of movement of all objects, except the Asteroid, in the game space to move
towards the location of the Asteroid. To let them move towards the Asteroid, we can
iterate through each of the potentially unknown objects in the game space area and simply
check if it supports the Moveable interface, then call its move method to let it move
towards the Asteroid.

61
Many developers favor using interfaces to provide a formal list of methods to support. In
fact, there’s a well known saying that good developers program to an interface not to an
implementation because it’s a developer’s choice how to implement those methods rather
than being provided with that code. If the chosen programming language supports
interfaces, then it is recommended to become familiar with how interfaces are
implemented in that language because interfaces are often a more future friendly way of
programming than using inheritance.

Aggregation

An
aggregation is another type of object relationship in which one object is built of other
objects. It is often referred to as a “has a”, “uses a”, or “uses many” relationship. Can also
be referred to as an object containing a number of another type of object.

Example indicated in the photo on


the left, where the class Fleet has
(a) Spaceship(s).

Aggregation relationships can be


represented in UML diagrams with
an unfilled diamond. With other
diagrams, indicating multiplicity
with an asterisk to say that a Fleet
can have anywhere from zero to
many Spaceship is optional.

62
Now though aggregation is commonly used, it may not always be worth showing on a
diagram because the object lifetimes of those Spaceships are not tied to each other or the
existence of the Fleet. When the Fleet object is destroyed it only breaks the bond of the
Spaceships, and these Spaceship objects can still exist independently without the Fleet
object.
Composition
A more specific form of aggregation is something called
composition. Like aggregation, composition is based around a
“has a” relationship between objects but it specifically implies
ownership. One can say that “A Spaceship has an Engine” but
more accurately it should be “A Spaceship owns the Engine.”
Composition implies ownership and an Engine has no meaning
or purpose in the system without a Spaceship. Likewise, the
Spaceship owns a Shield and owns a Weapon. The Spaceship is
composed of several objects that each have their own attributes
and behaviors.

The key difference between composition and aggregation is that in


a composition situation, if the owning object is destroyed, the contained objects are
destroyed too. Although aggregation may not always be worth showing in a UML
diagram, composition often can be and it’s represented with a filled diamond.

If the lifetime of an object is dependent on another object’s existence, that’s usually worth
showing, if for nothing more than to prompt the idea that, when defining the owning
class, writing a constructor and destructor methods to take care of creating and deleting
those internal objects.

63
As with aggregation, multiplicity symbols can be used to indicate that an object is
composed of one or more other objects.

64
Chapter 7 - Software Development
OOP support in different languages
Throughout this course, we've focussed on object-oriented design, the processes and
techniques to create a system. And while everything we've covered can be applied to any
object-oriented language, it's worthwhile to take a look at some of the differences
between the most common ones.

Check the image below for a brief comparison of each programming language’s
implementation on different object-oriented concepts.

You'll notice that different languages often use similar terminology. And they'll have
different terms for similar features. Or, they'll use the same term to mean different things.
Despite all these differences, all the concepts we've covered in this course apply to any
object-oriented language you choose to use.

65
General development principles
There are always multiple ways to write a piece of code to accomplish the same thing,
and programmers love to argue which ways are better. However, no matter which
solution to choose, there are a few general principles to keep in mind that'll make
maintaining the code base easier in the long run.

SOLID
One set of well-known principles has the acronym SOLID, representing five separate, but
interrelated principles that apply to any object oriented design, and are intended to make
your software more understandable, flexible, and maintainable. For example, the single
responsibility principle warns programmers to avoid creating what are called God objects,
objects that do a whole lot of things that aren't related to each other. Those behaviors
should be split between multiple, smaller classes, that each have one primary
responsibility, one reason to exist. If any of your classes start getting too big, always
consider if perhaps they should be split into two or more classes that interact with each

66
other. Now, there's a lot more behind these SOLID principles, and they warrant an entire
course of their own.

DRY

Another
common principle that forms an acronym, but is much simpler to understand, is Don't
Repeat Yourself or DRY. You should avoid copying and pasting large sections of code
without any changes because if you realize later that you made a mistake in one of those
sections, you'll have to remember all of the places you've copied it and fix those too.
That's a maintenance nightmare, and the DRY principle applies to more than just
software. We also want to avoid duplication in things like documentation, diagrams, and
database schemas. There should only be a single source that can be referenced elsewhere.

YAGNI

The
last principle I have for you that makes an acronym is YAGNI, which stands for You
Ain't Going to Need It. A common trap that many new, overzealous programmers fall
into is trying to make their code too extensible, and adding hooks for every possible
variation of everything they could ever possibly see. Abstraction is good, and we want to
be able to extend our programs, but abstracting too much will mean more testing, more
debugging, and code bloat. We don't want to waste time on things that will never be used.

67
Now, there are plenty of tools out there that will plug right into an IDE to do what's called
static analysis and highlight some of these things, duplicate code, unnecessary code, or
God objects, are examples of some code smells. The code could be valid, it could
compile, it could technically work for the problem we're looking to solve, but there's
something that just doesn't pass the sniff test. Of course, automated tools won't find
everything, and sometimes they'll flag the wrong thing, but many are configurable, and
they're a useful tool to help develop a well-designed program. If you want to learn more
about code smells, how to recognize them, how to fix them, I recommend checking out
some of the other courses on good programming practices.

Software testing
As exciting as releasing software can be, developing and running a proper test system is
just as important as developing the application itself.

Documentation, getting starter guides, and training are all good, and can help make a
basic user become a power user. The software should be easy and intuitive to use.

There are some of the usability considerations to keep in mind when creating software. It
should have been outlined as part of a use case. Some issues will come up when creating
use cases, but we can never anticipate how all our users will choose to use our software.
A good thing to always keep in mind is that if there's an optional field, input, or tool,
there will always be a user that tries to use it in a way that we were not intending them to.
Proper error messages and prompts go a long way to help guide them.

Testing all possible scenarios in every software can be pretty tedious. And no one really
wants to spend hours, days, or even weeks, testing the same thing over and over again.
That's why creating automated unit tests and system tests as you develop your program is
invaluable. Not only will it keep you from doing the same tedious tests over and over

68
again, as things change you can more readily find out if something breaks after that
change.

Features that used to be working fine suddenly stopped working is one common
complaint when users upgrade software. And it's often caused by developers refactoring
between the code releases. Maybe they've discovered a better way to implement a part of
the solution, or maybe refactoring was necessary to allow them to add a new feature.
Whatever the case may be, refactoring is a natural part of maintaining software over time.
Testing it to make sure everything still works the same as before should be done, and
having a well established automated testing system that properly mimics how users
interact with the software will help with that.

Design patterns
Creating software that merely works is tough enough, but writing code that's also flexible,
maintainable, and extensible, that's a real challenge. You should expect your code to
undergo multiple changes throughout the development process. And even after being
released, most modern applications receive updates to improve features or fix bugs. If
you don't structure your code well from the beginning, making those changes can be a
major burden.

Design patterns are common, repeatable solutions for creating software programs. They
define code architectures and best practices for solving common software design
problems that occur again and again across all kinds of applications, from business apps
to games.

Now, design patterns aren't strict standards with exact lines of codes to use, rather they're
templates to help structure your code in a smart way. So you'll spend less time refactoring
it and more time adding new cool features.

69
Design patterns became well known from this book, Design Patterns published by a
group of authors known as the Gang of Four . Their book details 23 design patterns,
which are organized into three groups. Creational patterns, focused on the instantiation of
objects and provide clever ways to have more flexibility in how objects are actually
created. The structural patterns describe how classes are actually designed. How things
like inheritance and composition and aggregation can be used to provide extra
functionality. And the behavioral patterns are specifically concerned with the
communication between objects as a program is running. In addition to the Gang of Four
book, Head First Design Patterns is another great resource that we highly recommend.
70
Conclusion
There's still a ton more to learn and understand about object-oriented programming,
analysis, and design. Now, many people first approach object orientation as a set of
absolute rules and predefined structures, but as further explored here, it's simply an
approach that brings with it a variety of tools and techniques. There's no one right way to
do object-oriented development, but there's certainly best practices and you have the
choice of which ones are most useful for you. As you continue to learn and grow as a
developer, we encourage you to revisit this content now and again. You'll find new ideas
and things that'll have new meaning the second or even third time around.

71

Вам также может понравиться