Skip to main content

CSC 321.01, Class 19: Design patterns, continued

Overview

  • Preliminaries
    • Notes and news
    • Upcoming work
    • Extra credit
    • Questions
  • Dependency injection
  • Observers
  • Factories
  • Strategy
  • Prototype

News / Etc.

  • Your pattern homeworks reminded me of why the Interweb is a danger.
  • We have two possible approaches to the last few days of class. I’ll let you choose between them.
    • Option 1: Work on a project, present next Monday, debrief next Wednesday.
    • Option 2: Wednesday and Friday off, some topic next Monday, debrief next Wednesday.
    • Option 3: Every student can choose whether they want to do option 1 or option 2.
    • You chose option 3; I’ll send you the project by the end of the day.
      • Yes, you can decide whether or not to do the project based on what the project is.
      • No, the project cannot lower your grade in this course.

Upcoming work

  • Optional project.

Good things to do

  • Lots of things in the Rosenfield Symposium on Technology and Human Rights.
  • CS Table, Tuesday at noon, Generating poetry
  • CS Extras, Thursday at 4:15 pm, The CS Curriculum

Design patterns

  • When we encounter similar problems, we often have similar solutions.
  • Patterns represent those problem/solution pairs

Dependency injection

Problem: Your program depends on a service of some sort. Many such services may be available, not always with the same interfaces/capabilities. How do we design our program so that it’s easy to switch/set which service to use?

Bad solution (anti-pattern)

   class Client
   {
     Service1 service1 = ...;
     Service2 service2 = ...;
     Service3 service3 = ...;
     Service4 service4 = ...;
     String choiceOfService;

     ....
     if (choiceOfService.equals("service1")) {
       service1.op(stuff);
     }
     else if (choiceOfService.equals("service2")) {
       service2.operation(stuff,morestuff);
     }
     else if (choiceOfService.equals("service3")) {
       service3.op(stuff,morestuff);
     }
     else if (choiceOfService.equals("service4")) {
       service4.procedure(stuff,morestuff,evenmorestuff);
     }
     else {
       throw new Exception("Unknown service: " + choiceOfService);
     }
   }
Why is this a bad solution?
Not very DRY. The lines are similar. (We could make an array of servicenames and services, and use a loop.)
To add another service, we have to change a lot of code.
It’s a pain to read.
To remove a service, we may have to change a lot of code.
All those tests are somewhat inefficient.
The client needs to keep track of a lot of services; that means when we make changes to those services, we have conversations we need to have.

What is a better solution?

class Client
{
  Service service;
  ...
  service.doThing(allparamsthatmaybenecessary);
}
interface Service
{
  void doThing(...);
}
class Service1Adapter
  extendsService1
  implements Service
{
  public void doThing()
  {
    super.op(subset_of_allparams);
  } // doThing
}

This is nice because it allows us to easily swap services.

Question: How does the client learn about the service?
When we construct the client, we provide the service.
Clients have a setService() method.
The client can read the service from a particular location on disk.
The client can query a “meta service” for available services.
Why is this called “dependency injection”?
Client depends on the Servers. We are injecting something in the middle of that dependency.
We are injecting the dependency into the client.
Which of the patterns we just learned seems close to dependency injection?
Claim: Prototype, because we’re looking at objects that may change their behavior.
Claim: Factory, because we’re potentially building one of several subtypes of objects. It also has a similar problem statement.
Claim: Strategy, because you’re using an interface to allow you to swap in different methods.
Claim: Not observer, but observers do have the same “extract dependencies.”

Observers

Problem: A one-to-many relationship, where you have a subject and one or many observers that want to know when the subject changes state.

Example: Almost any event driven system.

Concrete example: Twitter

Note: It’s much better to have the subject inform the observers than to have the observers repeatedly query the subject.

Note: Useful for MVC.

Solution: Subjects keep track of their observers. We typically have an addObserver method. Each Observer has a notifyMe method that the Subject is supposed to call. In the implementation of Subject, we need to make sure to call notifyMe for each observer.

Note: The list of observers is probably an ArrayList.

Note: Subjects ahve a variety of methods that may change their state.

Note: If we have addObserver, we should probably have removeObserver.

Note: Sometimes we still want to permit observers to directly query the subject.

Factories

Problem: Want to make a program portable across multiple operating systems and databases and what to make that portability clear.

Problem, more general: You want to produce an object in some class, but you aren’t always sure what subtype of the class you want, you might want a factory.

  if (type.equals("circle"))
    thingy = new Circle(...);
  else if (type.equals("square"))
    thingy = new Square(...);

vs.

  interface Factory
  {
    Thing create(...);
  }
  public class CircleFactory
  {
    Thing create(...)
    {
      return new Circle();
    }
  }

  thingy = myfactory.create(...);

Concrete example: Video games

Strategy

Problem: We might have different algorithms that are similar. Choosing which one can be complex /hard to read.

Bad strategy: A long case statement.

Example: Sorting.

Solution: Encapsulate them in an interface-like thing. Have general calls.

public interface Sorter<T implements Comparator>
{
  public void sort(T[] vals);
} // Sorter<T>

public class SelectionSorter implements Sorter
{
}

public class LuckSorter implements Sorter
{
}

Prototype

Problem: Creating a new object is expensive, so we can grab an object without having to create a new one.

Example: Grabbing data from the database. Instead of grabbing it again, clone the object that already has the data. Kind of like a flyweight.

Prototype: A different model of making objects. Traditionally: We define classes and call new. We also define subclasses. But what when we want “one-off” subclasses? Start with objects. Every object has clone. Every object has seams, so you can add new fields, methods.