CSC 321.01, Class 18: Design patterns
Overview
- Preliminaries
- Notes and news
- Upcoming work
- Friday PSA
- Questions
- Why design patterns?
- Reading reactions
- Flyweight
- Iterator
- Observer
- Adaptor
- Factory
News / Etc.
- Food-like substances!
Upcoming work
- For Monday: Write a thorough description of one design pattern (as assigned by Sam).
Good things to do
- Lots of things in the Rosenfield Symposium on Technology and Human Rights.
- CS Table, Tuesday at noon, Essays and Poetry
- CS Extras, Thursday at 4:15 pm, The CS Curriculum
Friday PSA
- I brought you BAC cards
- Be responsible
- Get enough sleep!
Questions
Why design patterns?
- Goal: Design better software
- As you design, you learn, you get better at what you do.
- Originally, express these lessons as “principles”
- Do this: In tests, use CONSTANT == VARIABLE rather than VARIABLE == CONSTANT
if (2 == x) ...
rather thanif (x == 2) ...
. (A good habit for catching the occasional “whoops, I only wrote one =”. - Don’t do that: “GOTO considered harmful”
- Think about this: Polymorphism
- Do this: In tests, use CONSTANT == VARIABLE rather than VARIABLE == CONSTANT
- In the world of architecture, there is a somewhat influential book
called “A Pattern Language” by Christopher Alexander.
- Goal
- Approach
- Example
- Applies to software, too. “In this situation, …”
- When building a large software system with a data store and a user interface, it is helpful to separate it into three systems with a well documented set of rules for communicating between those systems. (MVC)
- Many people love patterns, and use them in many situations
- CS Education Patterns
- Functional patterns
- DEVops patterns
- Some people hate! patterns
- Can make programming feel like following boilerplate
- Some people shoehorn problems into patterns
- Limits freedom of thought
- Most people are somewhere in the middle
- Good things to know about because they can help you think about solutions and can reveal new approaches
- Provide a vocabulary for talking to other programmers
- You will benefit from knowing some of the vocabulary and some of the things these patterns reveal
Reading reactions
What new design principle or pattern did you find most useful?
- Adapter, Proxy, Facade, Bridge
- Builder
- Command
- Dependency injection
- Interpreter
- Liskov substitution [x3]
- Observer
- Open/closed [x2]
- Proxy
- Sequence diagram
- Single responsibility principle [x5]
- Template method
Which design principle or pattern did you find the most relevant to your prior experience with object-oriented programming? Explain the pattern and why you chose it.
- Iterator [x3]
- Data clump
- Decorator
- Demeter [x2]
- Dependency injection
- Liskov substitution [x4]
- Observer
- Open/closed [x2]
- Single responsibility principle [x4]
- State [x2]
Most confusing
- Adapter, Proxy, Facade, Bridge
- Chain of responsibility
- Composite
- Decorator [x3]
- Delegation
- Demeter principle
- Dependency injection [x4]
- We’ll talk about it on Monday
- Interface segregation
- Liskov substitution [x3]
- Subtype polymorphism
- Open/Closed [x2]
- Single responsibility [x2]
- Strategy
- Template method
Flyweight
Problem: Some programs with lots of objects want to treat the objects as separate, even though the objects are very similar.
Philosophy: Find a way to share data.
Example: In setting type digitally, I have either a grid of pixels or a set of curves to represent each character. “[A]lphabeta” Both “a”s have the same pattern, so there’s no reason to store the pattern in the character. The class Character should be a flyweight.
class CharacterShape
{
BigChunkOfData 8pt;
BigChunkOfData 10pt;
BigChunkOfData 12pt;
BigChunkOfData 14pt;
BigChunkOfData 16pt;
void render(int x, int y, int size)
{
...
}
}
class Character
{
CharacterShape shape;
void render(int x, int y, int size)
{
shape.render(x,y,size);
}
}
class Document
{
Character[] data;
...
}
Note: Character delegates to CharacterShape.
Iterator
Problem: We want to do something to/with each element of a collection.
Traditional bad solution: Have something related to the collection that
explicitly speaks to the position. (E.g., if the collection is an array,
we have an i
; if the collection is a list, we have a field called current
)
- May allow you to access data that you don’t have.
- The client may not understand what the underlying representation is, and thereby start or stop at the wrong place.
- Violates the principle of encapsulation; the client has to know way too
much about the collection.
- If the collection implementation changes, the client code may break (or may break other things)
- Sometimes you want multiple simultaneous iterations. (Yes, there is a paper called “Lists with current considered harmful.”)
Solution: Allow classes to return Iterator objects. Normal model is Iterators provide advance, get, and maybe retreat and delete and replace.
Example: Deleting duplicates in a list.
Note: Iterators, like many of the core patterns, are increasingly built into languages
Adaptor
Problem: I want something that does X, I have something that does x. X and x are similar.
Solution: Build an object that responds to the needs of X by calling the similar things in x.
Example: I have a queue.
public class Queue<T>
{
void enqueue(T val);
T dequeue();
}
I want a ToDoList
public interface ToDoList<T>
{
void addTask(T task);
T nextTask();
}
We create
public class QueuedToDoList<T>
implements ToDoList<T>
{
Queue stuff;
void addTask(T task)
{
stuff.enqueue(task);
}
T nextTask()
{
return stuff.dequeue()
}
}
Or
public class QueuedToDoList<T>
extends Queue
implements ToDoList<T>
{
void addTask(T task)
{
this.enqueue(task);
}
T nextTask()
{
return this.dequeue()
}
}
Adaptors are one of many kinds of what most of us call “Wrappers”: They take an element of a class and “wrap” something around them (new functionality, additional functionality, etc.)
Example: We have a class, Course. Courses have multiple subclasses, EasyCourse, HardCourse. We decide we want to log each time a method is called.
public interface Course
{
int getMyGrade();
}
public class EasyCourse
implement Course
{
int getMyGrade()
{
return 90 + random(15);
}
}
public class HardCourse
implement Course
{
int getMyGrade()
{
if (random(20) == 1)
return 90
else
return 70
}
}
public class CountingCourse
implements Course
{
static int total_counter = 0;
int my_counter = 0;
Course trackme;
public CountingCourse(Course _trackme)
{
this.trackme = trackme;
}
public int getMyGrade()
{
++total_counter;
++my_counter;
return trackme.getGrade();
}
}
public class LoggingCourse
implement Course
{
}
This only counts when we wrap the courses.
Old version
Course csc151 = new EasyCourse(...);
Course csc341 = new HardCourse(...);
....
New version
Course csc151 = new CountingCourse(new EasyCourse(...));
Course csc341 = new CountingCourse(new HardCourse(...));
....
System.out.println("There were " + CountingCourse.total_counter + " calls.");
System.out.println("CSC 151 accounted for " + ((CountingCourse) csc151).my_counter);
Logging version
Course csc151 = new LoggingCourse("csc151", new EasyCourse(...));
Course csc341 = new LoggingCourse("csc341", new HardCourse(...));
....
welcome to the world of decoration!