Design Patterns

CSC-430

Phillip Wright

Design Patterns

Design patterns are, ultimately, nothing more than the result of applying a few object oriented principles which you should try to follow.

Design Patterns II

We will be learning those principles, but it is also good to study some of the patterns that arise from their use so that we don’t have to reinvent the wheel.

Design Patterns III

Learning established patterns allow you to quickly get to a solution, and they also provide you and other developers with a shared vocabulary that can be used to discuss your code.

Design Patterns Are Garbage?

During the semester, we will also be discussing how design patterns are actually kind of awful and may be seen as a “least awful” solution to problems in many cases.

Strategy Pattern

The strategy pattern defines a family of algorithms, encapsulates each, and makes them interchangeable. A strategy lets the algorithm vary independent from clients that use it

Strategy Pattern II

Before we take that definition apart, let’s take a look at the object oriented principles that lead to this pattern:

  • Encapsulate what varies
  • Program to interfaces
  • Favor composition over inheritance

Encapsulate What Varies

Modifying code is usually pretty dangerous, because it can introduce regressions in your code.

Encapsulate What Varies II

If we do not properly encapsulate our code, then simple changes can have far reaching impact.

Accordingly, we would prefer to identify behavior that may vary in our code and isolate it from code that does not vary.

Encapsulate What Varies III

If we do this successfully, then the non varying code can be written, tested, and left alone forever.

…assuming our testing was sufficient

Example

For instance, let’s assume we have the following code

public class Duck{
  private final int id;

  public Duck(final int id{
    this.id = id;
  }

  public String fly(){
    // Let's imagine this is a more complex computation
    return "I'm flying";
  }

  public int getId(){ return id; }  
}

Example II

In this example, we can be relatively sure that the id related code will not change. However, it is not unlikely that different ducks might fly differently.

Example III

So, we might do this instead

public class Duck{
  private final int id;
  private final FlyBehavior flyer = new FlyBehavior();

  public Duck(final int id){
    this.id = id;
  }

  public String fly(){ return flyer.fly(); }
  public int getId(){ return id; }
}

Example IV

public class FlyBehavior{
  public String fly(){
    return "I'm flying";
  }
}

Now, if the flying behavior needs to change, we still never have to handle the Duck class again (almost…).

Example V

We still have a slight problem. Our Duck can only store the class FlyBehavior, so the result isn’t that flexible.

How can we support different flying behaviors?

Program to an Interface

If we write code which uses specific, concrete types, we are stuck with those types and have to manually modify our code, duplicate code and do other awful things to use other types.

Program to an Interface II

If, instead, we program to more abstract interfaces, we can modify the behavior of our code, without modifying the code itself.

Program to an Interface III

Note that, in this context, interface refers to a conceptual interface which can be coded in the form of

  • an interface
  • an abstract class
  • a common super type
  • a function signature

Example VI

public class Duck{
  private final int id;
  private final FlyBehavior flyer;

  public Duck(final int id, final FlyBehavior flyer){
    this.id = id;
    this.flyer = flyer;
  }

  public String fly(){ return flyer.fly(); }
  public int getId(){ return id; }
}

Example VII

Now, we can create various subclasses for FlyBehavior and swap them in and out to customize how ducks fly.

Even cooler, we can do this at runtime!

Favor Composition

When we want to extend the behavior of our code, we typically have two ways to do it. We can either use inheritance or composition.

Inheritance

Using inheritance, A class A can extend another class B and override or add methods to obtain the desired behavior.

We often say that such an instance of A “is a” B.

Composition

We could instead compose objects and include a field of type C inside of a class B and the use that to change the behavior of the class B.

In this case, we would say that B “has a” C

Favor Composition II

We will learn that it is often valuable to write software so that different components are “decoupled” from each other. This is almost always easier when using composition.

Strategy Pattern III

At this point, we have reached a nice clean solution which is, in name, the strategy pattern.

As you see, we were able to reach this point only using basic principles, but it would have been easier to just jump straight to this design!

When To Use

You should consider using the strategy pattern when:

  • Many classes differ only in some type of behavior
  • You need different variations of an algorithm
  • An algorithm uses data that clients shouldn’t know about
  • A class defines many behaviors selected by a conditional

Problems

Some drawbacks of the strategy pattern include:

  • clients must be familiar with the strategies
  • various strategies may require different parameters
  • If stateful, there could be a large number of classes required

Is It Garbage?

We know that an interface that contains a single method can be replaced with a lambda expression.

This means we don’t have to define a special interface for the strategy: we just need to specify a general function interface as a parameter.

Is It Garbage? II

Additionally, we are passing strategies into constructors, but we could simply pass them into the methods where they are needed.

Storing them in a field is just a convenience.

Is It Garbage? III

At this point, we have essentially reached the basic concept of higher ordered functions

A higher ordered function is a function which takes another function as a parameter

Is It Garbage? IV

If the strategy pattern is basically just a complicated implementation of higher ordered functions, then what is the point of jumping through extra hoops?

As Java incorporates more functional concepts, design patterns like this start to become much less interesting.