设计模式
predefinition
UML
UML是让系统可视化、让规格和设计文档化的表现方法,全称为Unified Modeling Language
类
UML中的类图用于表示类、接口、实例等之间相互的静态关系。
类与层次结构
上图展示了ParentClass和ChildClass两个类之间的关系,空心箭头表明了两者之间的层次关系,箭头由子类指向父类,即继承。反过来说,ChildClass是ParentClass的子类。父类也成为基类或者超类,子类也成为派生类。
图中的长方形表示类,内部被横线自上而下分为类名、字段名、方法名。子类一定指向父类
接口与实现
带有空心三角形的虚线箭头代表了接口与实现类的关系,箭头从实现类指向接口。
聚合
只要在一个类中持有另外一个类的实例,无论是一个还是多个,它们之间的关系就是聚合关系。UML中使用带有空心菱形的实线表示聚合关系。
可见性(访问控制)
+
表示public方法和字段,可以从类外部访问这些方法和字段
-
表示private方法和字段,无法从类外部访问这些方法和字段
#
表示protected方法和字段,能够访问这些方法和字段的只能是该类自身、该类的子类以及同一包中的类
~
表示同一包中的类才能访问的方法和字段
类的关联
可以在类名前面加上黑三角表示类之间的关联关系。
时序图
UML的时序图用来表示程序在工作时其内部方法的调用顺序,以及事件的发生顺序。
类图中表示的是“不因时间流失而发生变化的关系”,时序图则与之相反,表示的是“随时间发生变化的东西”。
处理流与对象间的协作
client、server、device是三个实例,向下延伸的线是生命线。生命线仅存在于实例的生命周期内。黑色实线箭头代表方法的调用,虚线箭头代表返回open方法。时序图的阅读顺序是沿着生命线从上至下阅读。遇到箭头时,顺着箭头所指的方向查看对象间的协作。
Iterator模式
Iterator模式用于在数据集合中按照顺序遍历集和。
示例
Aggerate 接口中只声明了一个iterator
方法,该方法会生成一个用于遍历集和的迭代器。
// Aggerate 接口
public interface Aggregate{
public abstract Iterator iterator();
}
Iterator 接口用于遍历集合中的元素,作用相当于循环语句中的循环变量。hasNext()
用于判断是否存在下一个元素,next()
用于获取下一个元素。
// Iterator 接口
public interface Iterator{
public abstract boolean hasNext();
public abstract Object next();
}
Book类是表示书的实体类。
// Book 类
public class Book{
private String name;
public Book(String name){
this.name = name;
}
public String getName(){
return name;
}
}
BookShelf是表示书架的实体类,实现了Aggerate接口以及其中的iterator
方法。
// BookShelf 类
public class BookShelf implements Aggerate{
private Book[] books;
private int last = 0;
public BookShelf(int maxsize){
this.books = new Book[maxsize];
}
public Book getBookAt(int index){
return books[index];
}
public void appendBook(Book book){
this.books[last] = book;
last++;
}
public int getLength(){
return last;
}
public Iterator iterator(){
return new BookShelfIterator(this);
}
}
BookShelfIterator类用于遍历书架,需要发挥Iterator
的作用,实现了Iterator
接口。bookShelf
字段是BookShelfIterator所要遍历的书架,index字段表示迭代器当前所指向的书的下标。
// BookShelfIterator 类
public class BookShelfIterator implements Iterator{
private BookShelf bookshelf;
private int index;
public BookShelfIterator(BookShelf bookShelf){
this.bookShelf = bookShelf;
this.index = 0;
}
public boolean hasNext(){
if (index < bookShelf.getLength()){
return true;
} else{
return false;
}
}
public Object next(){
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
使用Main类来执行所有代码。
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
Iterator it = bookShelf.iterator();
while (it.hasNext()) {
Book book = (Book)it.next();
System.out.println(book.getName());
}
}
}
上述代码设计了能容纳4本书的书架,然后按照书名的英文字母顺序添加进入书架内。通过bookShelf.iterator()
得到的实例it
是用于遍历书架的Iterator实例。
解析
Iterator中出现的角色包括Iterator
、ConcreteIterator
、Aggregate
、ConcreteAggregate
。
Iterator
定义按顺序逐个遍历元素的API。
ConcreteIterator
实现Iterator所定义的API。
Aggregate
定义创建Iterator的API。
ConcreteAggregate
实现Aggregate所定义的API。
类图如下:
**引入Iterator后可以将遍历与实现分离开来。**无论实体类如何变化,只要iterator
方法能正确地返回实例,即使不对核心代码(while循环)做任何修改,代码都可以正常工作。这也就是设计模式的一个重要作用:帮助编写可复用的类。
如果只是用具体类来解决问题,容易导致类之间的强耦合,这些类也难以被作为组件再次利用,为了弱化类之间的耦合,使得类更容易作为组件被再次利用,需要引入抽象类和接口。
在Java中,没有使用的对象实例会自动被删除(垃圾回收)。因此在iterator中不需要与其对应的deleteIterator方法。
Adapter模式
用于填补“现有的程序”和“所需的程序”之间差异的设计模式就是Adapter模式。Adapter模式也被称为Wrapper模式。
Adapter有以下两种:
- 类适配器模式(使用继承的适配器)
- 对象适配器模式(使用委托的适配器)
示例
扮演适配器角色的是PrintBanner
,该类继承了Banner
并实现了需求——Print
接口。
Banner类是当前已有的类
// Banner 类
public class Banner{
private String string;
public Banner(String string){
this.string = string;
}
public void showWithParen(){
System.out.println("(" + string + ")");
}
public void showWithAster(){
System.out.println("*" + string + "*");
}
}
Print接口时需求的接口
// Print 接口
public interface Print{
public abstract void printWeak();
public abstract void printStrong();
}
PrintBanner类扮演适配器的角色,继承了Banner类,实现了Print接口。
// PrintBanner 类
public class PrintBanner extends Banner implements Print{
public PrintBanner(String string){
super(string);
}
public void printWeak(){
showWithParen();
}
public void printStrong(){
showWithAster();
}
}
Main类通过适配器PrintBanner类来进行任务执行
public class Main{
public static void main(String[] args){
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
类适配器的优点:直接通过继承现有类和实现目标接口来完成适配,代码简洁。缺点:由于Java不支持多重继承,如果现有类不是接口,则无法同时继承另一个类来扩展功能。同时与现有类之间存在强耦合,如果现有类发生更改,适配器也要做相应的更改。
Java中,委托指将某个方法中的实际处理交给其他实例的方法。
Main类和Banner与上面的代码相同,我们假设Print不是接口而是类。利用Banner类实现一个类,该类的方法和Print类的方法相同。
// Print 接口
public abstract class Print{
public abstract void printWeak();
public abstract void printStrong();
}
// PrinBanner 类
public class PrintBanner extends Print{
private Banner banner;
public PrintBanner(String string){
this.banner = new Banner(string);
}
public void printWeak(){
banner.showWithParen();
}
public void printStrong(){
banner.showWithAster();
}
}
解析
Adapter模式中有以下角色:
Target
定义所需的方法
Client
使用Target角色所定义的方法进行具体处理
Adaptee
持有既定方法的角色
Adapter
使用Adaptee角色的方法来满足Target角色的需求,这是Adapter模式的目的,也是Adapter角色的作用。
类图如下:
继承:
使用委托
什么时候使用Adapter模式:
Adapter模式会对现有的类进行适配,生成新的类。通过该模式可以很方便地创建我们需要的方法群。当出现bug时,由于我们很明确地知道bug不在现有的类(adaptee)中,所以只需要调查Adapter的类即可。
使用Adapter模式可以在完全不修改现有代码的前提下使现有代码适配于新的接口。在Adapter模式中也并非一定需要现成的代码,只要知道现有类的功能,就可以编写出新的类。
Template Method模式
在父类中定义处理流程的框架,在子类中实现具体处理的模式就是Template Method模式。
示例
AbstractDisplay类中定义了display
方法,方法中依次调用了open()
,print()
,close()
3个方法。虽然3个方法已经在AbstractDiplay中被声明了,但都是没有实体的抽象方法,调用抽象方法的display
就是模板方法。
// 抽象类 AbstractDisplay
public abstract class AbstractDisplay{
public abstract void open();
public abstract void print();
public abstract void close();
public final void display(){
open();
for (int i = 0; i < 5; i++){
print();
}
close();
}
}
public class CharDisplay extends AbstractDisplay { // CharDisplay是AbstractDisplay的子类
private char ch; // 需要显示的字符
public CharDisplay(char ch) { // 构造函数中接收的字符被
this.ch = ch; // 保存在字段中
}
public void open() { // 在父类中是抽象方法,此处重写该方法
System.out.print("<<"); // 显示开始字符"<<"
}
public void print() { // 同样地重写print方法。该方法会在display中被重复调用
System.out.print(ch); // 显示保存在字段ch中的字符
}
public void close() { // 同样地重写close方法
System.out.println(">>"); // 显示结束字符">>"
}
}
public class StringDisplay extends AbstractDisplay { // StringDisplay也是AbstractDisplay的子类
private String string; // 需要显示的字符串
private int width; // 以字节为单位计算出的字符串长度
public StringDisplay(String string) { // 构造函数中接收的字符串被
this.string = string; // 保存在字段中
this.width = string.getBytes().length; // 同时将字符串的字节长度也保存在字段中,以供后面使用
}
public void open() { // 重写的open方法
printLine(); // 调用该类的printLine方法画线
}
public void print() { // print方法
System.out.println("|" + string + "|"); // 给保存在字段中的字符串前后分别加上"|"并显示出来
}
public void close() { // close方法
printLine(); // 与open方法一样,调用printLine方法画线
}
private void printLine() { // 被open和close方法调用。由于可见性是private,因此只能在本类中被调用
System.out.print("+"); // 显示表示方框的角的"+"
for (int i = 0; i < width; i++) { // 显示width个"-"
System.out.print("-"); // 组成方框的边框
}
System.out.println("+"); // /显示表示方框的角的"+"
}
}
public class Main {
public static void main(String[] args) {
AbstractDisplay d1 = new CharDisplay('H'); // 生成一个持有'H'的CharDisplay类的实例
AbstractDisplay d2 = new StringDisplay("Hello, world."); // 生成一个持有"Hello, world."的StringDisplay类的实例
AbstractDisplay d3 = new StringDisplay("你好,世界。"); // 生成一个持有"你好,世界。"的StringDisplay类的实例
d1.display(); // 由于d1、d2和d3都是AbstractDisplay类的子类
d2.display(); // 可以调用继承的display方法
d3.display(); // 实际的程序行为取决于CharDisplay类和StringDisplay类的具体实现
}
}
解析
Template Method中出现了下面的角色
AbstractClass
负责实现模板方法,同时负责声明在模板方法中所使用到的抽象方法。
ConcreteClass
负责具体实现AbstractClass中定义的抽象方法,这里实现的方法将会在AbstractClass角色的模板方法中被调用。
Template Method模式的优点:由于在父类的模板方法中编写了算法,因此无需在每个子类中再编写算法。Template Method模式中,父类和子类是紧密联系、共同工作的。因此,在子类中实现父类中声明的抽象方法时,必须要理解这些抽象方法被调用的时机。
在示例中,不论是CharDisplay的实例还是StringDisplay的实例,都是先保存在AbstractDisplay类型的变量中,如何再来调用display方法。使用父类类型变量保存子类实例的优点是,即使没有用instanceof等指定子类的种类,程序也能正常工作。(里氏替换原则)
- 在子类中可以使用父类中定义的方法
- 可以通过在子类中增加方法以实现新的功能
- 在子类中重写父类的方法可以改变程序的行为
声明抽象方法是希望达到以下目的:
- 期待子类去实现抽象方法
- 要求子类去实现抽象方法(子类责任)
Factory Method模式
在Template Method 模式中,父类中规定处理的流程,在子类中实现具体的处理。如果将该模式用于生成实例,就演变为Factory Method模式。
在Factory Method模式中,父类决定实例的生成方式,但并不决定所要生成的具体的类,具体的处理全部交给子类负责,这样就可以将生成实例的框架和实际负责生成实例的类解耦。
示例
// Product类
package framework;
public abstract class Product {
public abstract void use();
}
// Factory类
package framework;
public abstract class Factory {
public final Product create(String owner) {
Product p = createProduct(owner);
registerProduct(p);
return p;
}
protected abstract Product createProduct(String owner);
protected abstract void registerProduct(Product product);
}
工厂用来调用create
方法生成Product实例。 create
方法的实现是先调用createProduct
生成产品,接着调用registerProduct
注册产品。
只要是Factory Method模式,就一定会用到Template Method模式。
// IDCard类
package idcard;
import framework.*;
public class IDCard extends Product {
private String owner;
IDCard(String owner) {
System.out.println("制作" + owner + "的ID卡。");
this.owner = owner;
}
public void use() {
System.out.println("使用" + owner + "的ID卡。");
}
public String getOwner() {
return owner;
}
}
// IDCardFactory类
package idcard;
import framework.*;
import java.util.*;
public class IDCardFactory extends Factory {
private List owners = new ArrayList();
protected Product createProduct(String owner) {
return new IDCard(owner);
}
protected void registerProduct(Product product) {
owners.add(((IDCard)product).getOwner());
}
public List getOwners() {
return owners;
}
}
import framework.*;
import idcard.*;
public class Main {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("小明");
Product card2 = factory.create("小红");
Product card3 = factory.create("小刚");
card1.use();
card2.use();
card3.use();
}
}
解析
Factory Method的类图:
Product
Product属于框架侧,定义了在Factory Method模式中生成的那些实例所持有的API,具体的处理由子类ConcreteProduct决定。
Creator
Creator署雨框架侧,负责生产Product的抽象类,具体的处理由子类ConcreteCreator决定。不用new关键字生成实例,而是调用生成实例的专用方法来生成,可以防止父类与其他具体类耦合。
ConcreteProduct
ConcreteProduct属于具体加工这一侧,决定了具体的产品。
ConcreteCreator
ConcreteCreator属于具体加工这一侧,负责生成具体的产品。
----------------------------------------------------------未完待续------------------------------------------------------