Solving specific problems which fit within the following categories.
managing layers of software
creating pipelines for data processing
dealing with user input
maintaining complex business rules
dealing with class hierarchies
Most widely used Design Patterns discussed
Builder
Command
Composite
Decorator
Event-Observer
Factory
Specification
Strategy
Visitor
Speaker: Gokul Subramanian
9. Is a square a rectangle?
class Rectangle {
private int len;
private int wid;
Rectangle(int l, int w) { len = l; wid = w; }
int getLength() { return len; }
void setLength(int l) { len = l; }
int getWidth() { return wid; }
void setWidth(int w) { wid = w; }
}
class Square extends Rectangle {
Square(int s) { super(s, s); }
int getSide() { return getLength(); }
void setSide(int s) {
setLength(s);
setWidth(s);
}
}
Rectangle sq = new Square(100);
sq.setLength(40);
10. Solution
class Rectangle {
private int len;
private int wid;
Rectangle(int l, int w) { len = l; wid = w; }
int getLength() { return len; }
void setLength(int l) { len = l; }
int getWidth() { return wid; }
void setWidth(int w) { wid = w; }
}
class Square {
private Rectangle rect;
Square(int s) {
rect = new Rectangle(s, s);
}
int getSide() { return rect.getLength();}
void setSide(int s) {
rect.setLength(s);
rect.setWidth(s);
}
}
11. But I want polymorphism to compute areas
interface Areable {
int getArea();
}
class Rectangle implements Areable {
…
@Override
public int getArea() {
return len * wid;
}
}
class Square implements Areable {
…
@Override
public int getArea() {
return rect.getArea();
}
}
List<Areable> areables = …
for (Areable ar : areables) {
System.out.println(ar.getArea());
}
This is what OOP is all
about.
21. Prescription
Define contracts by creating interfaces
Keep interfaces small and mutually exclusive
Add behavior by implementing these interfaces
Depend only on interfaces, not implementations
Depend only on direct dependencies
22. Further reading + coding
https://github.com/gokul2411s/patternsrepo
Read the Gang of Four book
Read books by Martin Fowler and Robert Martin (Uncle Bob)
Experience
23. Duh where’s the brand?
Google
Susquehanna International Group
UPenn GRASP robotics
Schlumberger
NIT Surathkal
Notes de l'éditeur
Good evening ladies and gentlemen. My name is Gokul. Today, we’ll learn about design patterns and architectural principles that can help us in both our day-to-day coding activities and the relatively rarer software design exercises.
There are two reasons I included this slide. Firstly, it’s the heading for what we are going to discuss here today. And secondly, because it represents an anti-pattern in making ppts. I took two good images (one of the Dancing Building in Prague, Czech Republic and another a wordart of today’s session) and superimposed them, and now the overall effect is somewhat unclean. We’ll see today how to avoid such mistakes in software.
This is my answer to whether design is an art or a science.
The reality is that it is a bit of both.
(Get a sense from the audience about OOP concepts.)
In OOP, we have objects, and the interaction between these objects constitutes a program. Objects are created from templates usually called classes. Classes encompass a structure consisting of sub-objects and behavior. At any point in the run of the program, the object has a certain state. The behavior may vary based on state.
With inheritance, we can express an IS-A relationship between objects. With such a relationship, we can extend the behavior of existing objects, by first inheriting their behavior and then adding more behavior. Thus, inheritance leads to a hierarchy.
Reasoning about a hierarchy of behaviors is quite tricky and can lead developers to write buggy code. As a result, there is a general principle that says “ Avoid inheritance of behavior, or in order words implementation inheritance”.
Here, we have implemented a File as a class, and provided the capability to write to the file via two methods write and writeMany. The latter is meant to provide a more efficient implementation, but in this case, its not really any more efficient. But that’s not the focal point here, so its ok.
Lets say we have a requirement to instrument file writes. As an example, consider the problem of counting the number of file writes. Instead of modifying the original file, which is say not in our control, we use inheritance to write a class called InstrumentedFile. This class inherits the behavior from the File class, but also overrides the write method to provide additional wrapping functionality. Since this takes care of both write and writeMany in the current implementation, all is fine.
However, the problem starts when the owner of the File class decides to make this change. In this case, the implementation of InstrumentedFile is now broken since it does not account for the writeMany methods any more.
Here, a change in one part of the codebase caused another part to break. This is called fragility and often a result of using inheritance to inherit behavior / implementation.
Here, we make File an interface and provide a skeletal (basic) implementation via an AbstractFile class, which is an abstract class and cannot be instantiated directly. Any subclass can now either directly implement the File interface or extent the AbstractFile class, depending on whether it wants the basic functionality or not. This is how the entire Java library is structured.
Then, we have the InstrumentedFile interface which adds the getWriteCount method. The implementation class InstrumentedFileImpl composes a File object and provides its functionality without introducing any coupling with the behavior of the composed File object.
These three images have more in common than they have different. They all represent the same person.
Maybe the shoes, clothing, hairdo and jewelry are different, but that does not change the fact that the same person has been decorated differently.
The decorator pattern says
Extend by decoration, not by inheritance.
The six sides of a dice are all different, and yet they belong to the same dice. The decorator, proxy, adapter and facade patterns have a similar relationship.
Proxies and decorators are similar because they provide additional functionality on top of an existing object. But proxies do so while adhering to the same interface and decorators enhance the interface. Thus, proxies can only provide implementation improvements like caching, or lazy loading etc, and decorators for new publicly visible behvaior.
Facades and adapters are similar because they adapt from one or more interfaces to a new one. But facades do so for simplifying and removing reliance of multiple smaller interfaces. And adapters do so for transforming one contract to another. How different the two contracts are depends on the scenario.
We all know that a square is a rectangle with equal sides. Here, we try to represent this fact via inheritance.
Here, we inherit the structure of a rectangle, but also behavior such as setLength and setWidth, which individually do not make sense for a Square. So, client code could mistakenly call the setLength method on a square and violate the invariant that the sides of a square are equal in length.
The reason for this problem is that the square has stricter constraints than a rectangle on structure, but the inherited behavior of the rectangle allows clients to violate these stricter constraints. While inheriting, we must keep in mind to ensure that the child class follows the Liskov substitution principle: “Be more forgiving in your inputs, but be stricter in your outputs.” This principle allows us to successfully use polymorphism.
We solved the problem by entirely avoiding inheritance. Simple use composition, because now, the behaviors of the rectangle, except those exposed by the square do not seep through.
The previous solution worked for us, and solved the problems arising due to implementation inheritance. But, what if we wanted polymorphism in order to treat rectangles and squares the same way for the purpose of computing areas?
This is actually quite simple. We can continue with the current structure of the two classes, but just add a new interface and have these classes implement this interface. This gives us the required polymorphism with reuse of code in a backward compatible manner.
What if we wanted to treat rectangles and squares similarly for the purposes of scaling the size? We could just add a new interface and implement it too. Thus, by creating small interfaces and adding them as required, we can accomplish our goals in an extensible manner.
In the previous exercise, we saw how to implement a square in terms of a rectangle. But suppose you had to implement a rectangle in terms of squares. How can we do this? The answer dates back to 300 BC when Euclid described an algorithm to compute the greatest common divisor of two integers. The geometric interpretation of this algorithm give us what we are looking for.
(Describe the algorithm.) The important matter here is that this geometric structure fits perfectly into the confines of a design pattern called the composite pattern. A composite pattern is a recursive structure. (Explain recursion.)
Examples: HTML, Linux file system etc.
(Show code and explain.)
This is Leonardo Da Vinci. He was into “invention painting sculpting architecture science music mathematics engineering literature anatomy geology astronomy botany writing history cartography” etc. He even invented the helicopter.
People with such talent and capabilities are often hard to understand. And it makes them cool.
But software that is hard to understand is not cool. So, we wanted to represent complex ideas in software, how can we make it easy to understand? We can do so by breaking down logic in small mutually-exclusive pieces, and then put together these pieces to obtain the required functionality. This makes for reusable code, because we can change the behavior of the application easily by wiring existing pieces differently or adding new pieces.
Consider an example. Here, we have different cars from our Indian carmaker: Tata. Each car is very complex, has different behaviors, but they also share lots of common components and behavior. What would we do if we have to do if we were to build software for representing these cars, and allow the addition of new cars easily?
Often, we have huge objects (such as a car), which host a lot of functionality and we want to compose them with lots of behavior. Here, we want to make sure that the resultant class structure is flexible, because we might soon want to compose other huge objects which have only slightly different behvaior.
This is where the strategy pattern comes in. It leverages the “composition over inheritance” adage. More formally, it allows us to break an application into small client-specific contracts (each with their implementations) and compose these contracts to obtain more complex functionality. (Show code and explain.)
When you ask someone where they live, they might give you wildly different answers, in part because they live in wildly different places. But usually, there is a lot of variance in the structure of their answer. Someone might give you the street they live on, someone the city, some humanitarian may tell you that they live on the kind and beloved planet earth, and a astronomer may tell you that we are all part of a tiny galaxy in the universe.
If you wanted to represent an address in software, you would have to allow for this variability. In the world of OOP, an Address class will have to provide exponentially many constructors, which is both a maintainability nightmare and also just not extensible. Imagine adding new field to the address object. The solution to this problem is the Builder pattern.
The builder pattern says:
Separate the specification of the object from the creation of the object.
(Show code and explain.)
Here is an image of a king trying to dominate a town and make sure that the town has no other dominant player / king.
This is representative of the Singleton pattern, which I am going to skip because it should not be used frequently. The only use cases for a Singleton object are when having more than one object of a certain kind can cause harm to your program, or it does not make logical sense to have more than one. E.g. Logger that writes to a log file, Database connections, caches.
There are lots of patterns out there and we cannot cover them all in the time we have today. And anyway, just rote learning patterns is not going to help in the long run.
So, lets take one step further and look at some toy problems which demonstrate the use of some more useful patterns. Once you go home, you can take up these problems and try to write code yourself, and see if you are able to make design patterns a natural part of your programming arsenal.