写在最前面
这篇用来给自己复习用,将之前自己写的十三个部分汇总在了一起,虽然不满23种设计模式。
适配器模式简介
在计算机编程中,适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。详见百度百科
适配器模式实际例子
即现有的类无法直接使用,需要先进行适当的变换。
现在,我的电脑的插头是三相的,如下:
public class Three {
int num= 3;//插头相数
public void plug(){
System.out.println("插头是" + num + "相");
}
}
但是,现在墙上的插座却是只有两项的插座,我又不能现在立刻把墙拆了重新装修,所以我必须要找一个适配器,让我的插头能插入两项的插座中。为了完成这个目标,先定义一个接口。
public interface Two {
public abstract void twoSocket();
}
该接口的twoSocket()方法要将原来的三相插头变成二项的。之后设计一Adapter类继承Three,实现Two。在twoSocket()方法里修改了插头数目num,然后调用原来的plug()方法。
public class Adapter extends Three implements Two{
@Override
public void twoSocket() {
num = 2;
plug();
}
}
最后,用Test类来测试结果
public class Test {
public static void main(String[] args) {
Two t = new Adapter();
t.twoSocket();
}
}
控制台输出结果:
插头是2相
可以看到,在Test类中是使用Two接口,调用twoSocket()方法。Test不需要知道原来Three类中的plug()方法具体实现,就相当于现在的两相插座,它被一个两相的插头插入,但是它不知道原来电脑是三相插头被转换成了两相插头。
类图
参考文献
1.《图解设计模式》
2.《设计模式之禅》
3.一个示例让你明白适配器模式
单例模式简介
定义:一个类只有一个实例。
类图如下:
可以看到Singleton自己创建了一个实例
singleton
,每次需要用到Singleton的实例时,使用getInstance()
函数返回唯一一个singleton实例,而不是使用Singleton singleton = new Singleton()
。通过这些操作过程可以保证只用到Singleton的同一个实例,而不是通过new
创建了多个实例。
单例模式使用
对于缓存,数据连接等情况,我们只需要一个实例来完成任务,创建过多的实例会浪费资源空间且不方便操作。此时使用单例模式可以解决上述问题。
平常在windows系统中打开的任务管理器便是一个单例模式,它保证了用户无法打开多个任务管理器。单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例,一种是懒汉式单例:
饿汉式单例模式代码实现:
public class Singleton {
private static Singleton instance=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
懒汉式单例模式代码实现:
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
单例实际例子
我之前完成的一个工程,有一个类时DataBaseImpl,它所需完成的功能是在指定APP路径下创建一个文件,然后完成对这个文件的读写,构造函数如下:
DataBaseImpl() {
filename = new File("/storage/emulated/0/Android/data/engineering.software.advanced.cantoolapp/files/canmsg-sample.dbc");//读取文件
}
对此我可以使用单例模式,使得他每次都使用同一个实例,因为从始至终都是对那个文件的读写。
public static DataBase getInstance() {
return dataBase;
}
参考文献
1.《图解设计模式》
2.《设计模式之禅》
Flyweight模式简介
一言以蔽之,“通过尽量共享实例来避免new出实例”。比如我现在需要使用一大串字符,这串字符里有大量的重复的字符,如果我每个字符都用一个新的内存空间来存储它,那么肯定会造成大量的空间浪费。为了解决这个办法,当使用相同字符的时候,我使用同一个内存空间,直接从以往的内存空间中取而不是新分配一个内存,这样就避免了大量的浪费。它使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。详见百度百科
Flyweight模式优点
- 享元模式的外部状态相对独立,使得对象可以在不同的环境中被复用(共享对象可以适应不同的外部环境)
- 享元模式可共享相同或相似的细粒度对象,从而减少了内存消耗,同时降低了对象创建与垃圾回收的开销
Flyweight模式缺点
- 外部状态由客户端保存,共享对象读取外部状态的开销可能比较大
- 享元模式要求将内部状态与外部状态分离,这使得程序的逻辑复杂化,同时也增加了状态维护成本
Flyweight模式实际例子
比如我现在需要画一些不同颜色的圆,我借助一个绘图程序,圆已经画好了,只需要往里面传入颜色就行。
首先定义绘图接口,只有一个方法那就是画图
public abstract interface Shape {
public abstract void draw();
}
在这之后继承这个接口,有一个私有变量color,代表颜色,实现draw方法画不同颜色的圆形。
public class Circle implements Shape{
private String color;
public Circle(String color){
this.color = color;
}
public void draw() {
System.out.println("画了一个" + color +"的圆形");
}
}
然后是定义享元工厂类,它主要是用来创建并管理Flyweight对象,它主要用来确保合理地共享Flyweight,在工厂中生成的Flyweight角色可以实现共享实例。
享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
import java.util.HashMap;
import java.util.Map;
public class FlyweightFactory {
static Map<String, Shape> shapes = new HashMap<String, Shape>();
public static Shape getShape(String key){
Shape shape = shapes.get(key);
//如果shape==null,表示不存在,则新建,并且保持到共享池中
if(shape == null){
shape = new Circle(key);
shapes.put(key, shape);
}
return shape;
}
public static int getSum(){
return shapes.size();
}
}
最后是测试结果。可以发现由享元工厂类生成共享实例shpe,传入参数颜色,然后进行画图操作。
public class Main {
public static void main(String[] args) {
Shape shape1 = FlyweightFactory.getShape("红色");
shape1.draw();
Shape shape2 = FlyweightFactory.getShape("灰色");
shape2.draw();
Shape shape3 = FlyweightFactory.getShape("绿色");
shape3.draw();
Shape shape4 = FlyweightFactory.getShape("红色");
shape4.draw();
Shape shape5 = FlyweightFactory.getShape("灰色");
shape5.draw();
Shape shape6 = FlyweightFactory.getShape("灰色");
shape6.draw();
System.out.println("一共绘制了"+FlyweightFactory.getSum()+"中颜色的圆形");
}
}
参考文献
- 《图解设计模式》
- 《设计模式之禅》
- 《设计模式读书笔记》
State模式定义
运行一个对象在其内状态改变时改变它的行为,对象看起来似乎修改了它的类。
State模式实际例子
举个例子来解释这个定义,比如我这个人一般有两个状态,一种状态是饿,另一种状态是饱。饿的时候会去吃饭,饱的时候就会去睡觉,现在我需要使用代码完成上述功能,如果我不使用State模式的话,一般会想到使用if条件判断来完成。
if(饿)
吃饭
else if(饱)
睡觉
但是在State模式中不这样,在State模式中,我们可以使用类来表示我饿还是饱。这样类中的方法不需要使用if来判断我的状态是否饥饿。
表示饿的类{
吃饭();
}
表示饱的类{
睡觉();
}
State模式代码实现
接下来是具体代码实现。首先定义一个Context类来表示具体的状态。
public class Context {
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}
接着我定义了一个状态接口,接口里根据传入的状态触发动作。
public interface State {
public void doAction(Context context);
}
我分别使用两个类来表示我的饿和饱的状态,这两个类都实现了State接口,并且可以显示当前的状态.
HungryState代表饿了。
public class HungryState implements State{
public void doAction(Context context) {
System.out.println("去吃饭");
context.setState(this);
}
public String toString(){
return "饿了";
}
}
FullState代表饱了。
public class FullState implements State{
public void doAction(Context context) {
System.out.println("去睡觉");
context.setState(this);
}
public String toString(){
return "饱了";
}
}
最后测试一下
public class Main {
public static void main(String[] args) {
Context context = new Context();
HungryState startState = new HungryState();
startState.doAction(context);
System.out.println(context.getState().toString());
FullState stopState = new FullState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
输出结果:
去吃饭
饿了
去睡觉
饱了
State模式优点
- 将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。
- 使得状态转换显式化。
- State对象可被共享。
State模式缺点
- 增加系统中类和对象的个数。
- 状态模式的结构和实现都比较复杂,使用不当会造成程序中代码的混乱。
参考文献
- 《图解设计模式》
- 《设计模式之禅》
- 《状态模式——菜鸟教程》
Observer模式定义
在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。
Observer模式优点
- 观察者与被观察者之间是属于轻度的关联关系,并且是抽象耦合的,这样,对于两者来说都比较容易进行扩展。
- 观察者模式是一种常用的触发机制,它形成一条触发链,依次对各个观察者的方法进行处理。但同时,这也算是观察者模式一个缺点,由于是链式触发,当观察者比较多的时候,性能问题是比较令人担忧的。并且,在链式结构中,比较容易出现循环引用的错误,造成系统假死。
Observer模式的结构
-
Subject(观察对象)
定义了注册观察者和删除观察者的方法,此外还声明了“获取现在的状态”的方法。 -
ConcreteSubject(具体的观察对象)
当自身状态发生改变后,它会通知所有已经注册的Observe角色。 -
Observe(观察者)
负责接收来自Subject角色的状态变化的通知,声明了update方法。 -
ConcreteObserver(具体的观察者)
当它的update方法被调用之后,会去获取要观察的对象的最新状态。
Java实现
在Java类库中的java.util.observer
接口和java.util.Observable
类就是一种Observer模式。Observer接口定义了update方法。void update(Observable o, Object arg);
Observable类的实例就是被观察的Subject角色,Object类的实例就是附加信息。
缺点:传给java.util.observer
接口的Subject角色必须是java.util.Observable
类型。Java只能单一继承,如果Subject角色已经是某个类的子类那么无法继承java.util.Observable
类。
Observer模式的实际例子
首先自定义了Observer接口,声明了update方法。
public interface Observer {
public void update();
}
之后定义观察对象的抽象类,定义添加,删除等方法。
import java.util.Vector;
public abstract class Subject {
private Vector<Observer> obs = new Vector();
public void addObserver(Observer obs){
this.obs.add(obs);
}
public void delObserver(Observer obs){
this.obs.remove(obs);
}
protected void notifyObserver(){
for(Observer o : obs)
o.update();
}
public abstract void doSomething();
}
定义了一个实际的观察对象
public class ConcreteSubject extends Subject{
public void doSomething(){
System.out.println("被观察者事件反生");
this.notifyObserver();
}
}
定义了两个实际观察者
public class ConcreteObserver1 implements Observer{
public void update() {
System.out.println("观察者1收到信息,并进行处理。");
}
}
public class ConcreteObserver2 implements Observer{
public void update() {
System.out.println("观察者2收到信息,并进行处理。");
}
}
测试
import java.util.Observer;
public class Main {
public static void main(String[] args) {
Subject sub = new ConcreteSubject();
ConcreteObserver1 concreteObserver1 = new ConcreteObserver1();
ConcreteObserver2 concreteObserver2 = new ConcreteObserver2();
sub.addObserver(concreteObserver1);
sub.addObserver(concreteObserver2);
sub.doSomething();
}
}
运行结果
被观察者事件反生
观察者1收到信息,并进行处理。
观察者2收到信息,并进行处理。
参考文献
- 《图解设计模式》
- 《设计模式之禅》
- 《极客学院观察者模式》
Mediator模式定义
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。
Mediator模式使用场景
在上学的时候,同学之间需要相互交流,这样的交流是一种网状结构,往往是你说一句我说一句,这个时候如果使用一个QQ群,来慢慢记录大家的意见,就会变成这样
实际上这里的QQ就是指的是中介,同学之间需要交流可以通过一个QQ群。
Mediator模式代码实现
有两个类A和B,类中各有一个数字,并且要保证类B中的数字永远是类A中数字的100倍。也就是说,当修改类A的数时,将这个数字乘以100赋给类B,而修改类B时,要将数除以100赋给类A。类A类B互相影响,就称为同事类。A与B之间必然要关联,但是使用中介者模式,类A类B之间则无需直接关联。
先定义抽象的同事类。
abstract class AbstractColleague {
protected int number;
public int getNumber() {
return number;
}
public void setNumber(int number){
this.number = number;
}
//中介者
public abstract void setNumber(int number, AbstractMediator am);
}
分别定义类A与B,均继承抽象类。
class ColleagueA extends AbstractColleague{
public void setNumber(int number, AbstractMediator am) {
this.number = number;
am.AaffectB();
}
}
class ColleagueB extends AbstractColleague{
@Override
public void setNumber(int number, AbstractMediator am) {
this.number = number;
am.BaffectA();
}
}
定义抽象的中介者类,将处理对象关系的代码由另一个类来完成。
abstract class AbstractMediator {
protected AbstractColleague A;
protected AbstractColleague B;
public AbstractMediator(AbstractColleague a, AbstractColleague b) {
A = a;
B = b;
}
public abstract void AaffectB();
public abstract void BaffectA();
}
定义具体类来实现中介者
class Mediator extends AbstractMediator {
public Mediator(AbstractColleague a, AbstractColleague b) {
super(a, b);
}
//处理A对B的影响
public void AaffectB() {
int number = A.getNumber();
B.setNumber(number*100);
}
//处理B对A的影响
public void BaffectA() {
int number = B.getNumber();
A.setNumber(number/100);
}
}
最后是测试类
public class Main{
public static void main(String[] args){
AbstractColleague collA = new ColleagueA();
AbstractColleague collB = new ColleagueB();
AbstractMediator am = new Mediator(collA, collB);
collA.setNumber(1000, am);
System.out.println("collA的number值为:"+collA.getNumber());
System.out.println("collB的number值为A的10倍:"+collB.getNumber());
collB.setNumber(1000, am);
System.out.println("collB的number值为:"+collB.getNumber());
System.out.println("collA的number值为B的0.1倍:"+collA.getNumber());
}
}
Mediator模式优点
- 简化了对象之间的交互。
- 将各同事解耦。
- 减少子类生成。
- 可以简化各同事类的设计和实现。
Mediator模式缺点
- 在具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
参考文献
- 《图解设计模式》
- 《设计模式之禅》
- 《极客学院——观察者模式》
Facade模式定义
外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。简而言之,它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。
Facade模式优点
- 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
- 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
- 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
- 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。
Facade模式缺点
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
Facade模式代码实现
PowerSystem类控制电视机的打开与关闭
public class PowerSystem {
public void powerOn() {
System.out.println("开机");
}
public void powerOff() {
System.out.println("关机");
}
}
ChannelSystem 类负责控制切换频道
public class ChannelSystem {
public void next() {
System.out.println("下一频道");
}
public void prev() {
System.out.println("上一频道");
}
}
VoiceSystem类负责控制调大调小音量
public class VoiceSystem {
public void turnUp() {
System.out.println("音量增大");
}
public void turnDown() {
System.out.println("音量减小");
}
}
最后用一个TvController类可以对上述功能统一进行处理
public class TvController {
private PowerSystem mPowerSystem = new PowerSystem();
private VoiceSystem mVoiceSystem = new VoiceSystem();
private ChannelSystem mChannelSystem = new ChannelSystem();
public void powerOn() {
mPowerSystem.powerOn();
}
public void powerOff() {
mPowerSystem.powerOff();
}
public void turnUp() {
mVoiceSystem.turnUp();
}
public void turnDown() {
mVoiceSystem.turnDown();
}
public void nextChannel() {
mChannelSystem.next();
}
public void prevChannel() {
mChannelSystem.prev();
}
}
测试代码
public class Main {
public static void main(String[] args) {
TvController tvController = new TvController();
tvController.powerOn();
tvController.powerOff();
tvController.nextChannel();
tvController.prevChannel();
tvController.turnUp();
tvController.turnDown();
}
}
运行结果
开机
关机
下一频道
上一频道
音量增大
音量减小
与其他相关模式
- 抽象工厂模式:Abstract Factory式可以与Facade模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。 Abstract Factory也可以代替Facade模式隐藏那些与平台相关的类。
- 中介模式:Mediator模式与Facade模式的相似之处是,它抽象了一些已有的类的功能。然而,Mediator的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能。 Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。相对而言,Facade模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道Facade的存在。 通常来讲,仅需要一个Facade对象,因此Facade对象通常属于Singleton模式。
- Adapter模式:适配器模式是将一个接口通过适配来间接转换为另一个接口。 外观模式的话,其主要是提供一个整洁的一致的接口给客户端。
参考文献
- 《图解设计模式》
- 《设计模式之禅》
- 《外观模式》
Chain of Responsibility模式定义
责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
Chain of Responsibility模式使用场景
在日常生活中,我们去某些地方办事,总是一个地方推卸给另一个地方,稍微引申一下,就是这里的责任链模式。有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。
Chain of Responsibility模式缺点
使用责任链模式可以推卸请求,直至找到合适的处理请求对象,这样确实提高了程序的灵活性,但是同时也导致了请求发生了延迟。这是一个需要进行权衡的问题,如果请求和处理者之间的关系是确定的,而且需要非常快的处理速度,不使用责任链模式更好。
Chain of Responsibility模式优点
弱化了发出请求的人和处理请求的人之间的俄关系,角色发出请求之后,然后请求之后会在职责链中传播,知道该请求被处理。
Chain of Responsibility模式代码实现
首先定义发生问题的Trouble类
public class Trouble {
private int number;
public Trouble(int number){
this.number = number;
}
public int getNumber(){
return number;
}
public String toString(){
return "[Trouble " + number + "]";
}
}
然后定义抽象类,负责解决问题。
public abstract class Support {
private String name;
public Support(String name) {
super();
this.name = name;
}
private Support next;
public Support setNext(Support next) {
this.next = next;
return next;
}
public final void support(Trouble trouble){
if(resolve(trouble)){
done(trouble);
} else if(next != null){
next.support(trouble);
} else{
fail(trouble);
}
}
public String toString(){
return "[" + name + "]";
}
protected abstract boolean resolve(Trouble trouble);
protected void done(Trouble trouble){
System.out.println(trouble + "is resolved by " + this + ".");
}
protected void fail(Trouble trouble){
System.out.println(trouble + "cannot be resolved.");
}
}
next字段指定要推卸给的对象,resolve方法是解决方法,返回true表示已经被处理,support方法会调用resolve方法,如果resolve方法返回false,则support方法会将问题交给下一个对象。
之后分别定义几个不同的处理类,都是support的子类
public class NoSupport extends Support{
public NoSupport(String name) {
super(name);
}
@Override
protected boolean resolve(Trouble trouble) {
return false;
}
}
public class LimitSupport extends Support{
private int limit;
public LimitSupport(String name, int limit) {
super(name);
this.limit = limit;
}
@Override
protected boolean resolve(Trouble trouble) {
if(trouble.getNumber() < limit){
return true;
} else{
return false;
}
}
}
public class OddSuport extends Support{
public OddSuport(String name) {
super(name);
}
@Override
protected boolean resolve(Trouble trouble) {
if(trouble.getNumber() % 2 == 1){
return true;
} else{
return false;
}
}
}
public class SpecialSupport extends Support{
private int number;
public SpecialSupport(String name, int number) {
super(name);
this.number = number;
}
@Override
protected boolean resolve(Trouble trouble) {
if(trouble.getNumber() == number){
return true;
} else{
return false;
}
}
}
最后是测试代码,分别定义几个不同的处理对象,然后构成一条链。
public class Main {
public static void main(String[] args) {
Support alice = new NoSupport("Alice");
Support bob = new LimitSupport("Bob", 100);
Support charlie = new SpecialSupport("CHarlie", 429);
Support diana = new LimitSupport("Diana", 200);
Support elmo = new OddSuport("Elmo");
Support fred = new LimitSupport("Fred", 300);
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
for(int i = 0;i < 500;i += 33)
alice.support(new Trouble(i));
}
}
运行结果
[Trouble 0]is resolved by [Bob].
[Trouble 33]is resolved by [Bob].
[Trouble 66]is resolved by [Bob].
[Trouble 99]is resolved by [Bob].
[Trouble 132]is resolved by [Diana].
[Trouble 165]is resolved by [Diana].
[Trouble 198]is resolved by [Diana].
[Trouble 231]is resolved by [Elmo].
[Trouble 264]is resolved by [Fred].
[Trouble 297]is resolved by [Elmo].
[Trouble 330]cannot be resolved.
[Trouble 363]is resolved by [Elmo].
[Trouble 396]cannot be resolved.
[Trouble 429]is resolved by [CHarlie].
[Trouble 462]cannot be resolved.
[Trouble 495]is resolved by [Elmo].
参考文献
- 《图解设计模式》
- 《设计模式之禅》
Composite 模式定义
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
Composite 模式使用场景
在计算机的文件系统中,有“文件夹”的概念,里面可以放入文件,也可以放入子文件夹。在子文件夹中,一样可以放入文件和文件夹。文件夹形成一种容器结构、递归结构。Composite模式就是用于创造出这一的结构模式。能够使得容器与内容物具有一致性,创造出递归结构的模式就是Composite模式。
Composite 模式代码实现
定义抽象类Entry,表示目录条目。目录条目有名字,有大小,可以通过get方法得到它们。想文件夹中放入文件和文件夹的方法是add方法。printList方法用于显示文件夹中的无内容,toString定义了实例的标准文字显示方式。
public abstract class Entry {
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatMentException{
throw new FileTreatMentException();
};
public void printList(){
printList("");
}
protected abstract void printList(String prefix);
public String toString(){
return getName() + "(" + getSize() + ")";
}
}
File类时Entry的子类,有一个表示文件名的name字段,还有表示文件大小的size字段。
public class File extends Entry{
private String name;
private int size;
public File(String name, int size){
this.name = name;
this.size = size;
}
public String getName(){
return name;
}
public int getSize(){
return size;
}
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
Directory类时Entry的子类,一个表示文件夹名字的name字段,还有用来保存文件夹中的目录条目的directory字段。getSize方法的地柜调用与Composite模式的结构是相对应的。
import java.util.ArrayList;
import java.util.Iterator;
public class Directory extends Entry{
private String name;
private ArrayList directory = new ArrayList();
public Directory(String name){
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
int size = 0;
Iterator iterator = directory.iterator();
while(iterator.hasNext()){
Entry entry = (Entry) iterator.next();
size += entry.getSize();
}
return size;
}
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
Iterator iterator = directory.iterator();
while(iterator.hasNext()){
Entry entry = (Entry) iterator.next();
entry.printList(prefix + "/" + name);
}
}
public Entry add(Entry entry){
directory.add(entry);
return this;
}
}
还有一个是对文件调用add方法是抛出的异常。
public class FileTreatMentException extends RuntimeException{
public FileTreatMentException(){
}
public FileTreatMentException(String msg){
super(msg);
}
}
最后是测试类
public class Main {
public static void main(String[] args) {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi",10000));
bindir.add(new File("latex",20000));
rootdir.printList();
System.out.println("");
System.out.println("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java",200));
hanako.add(new File("memo.tex",300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
rootdir.printList();
}
}
显示结果
Making root entries...
/root(30000)
/root/bin(30000)
/root/bin/vi(10000)
/root/bin/latex(20000)
/root/tmp(0)
/root/usr(0)
Making user entries...
/root(31500)
/root/bin(30000)
/root/bin/vi(10000)
/root/bin/latex(20000)
/root/tmp(0)
/root/usr(1500)
/root/usr/yuki(300)
/root/usr/yuki/diary.html(100)
/root/usr/yuki/Composite.java(200)
/root/usr/hanako(300)
/root/usr/hanako/memo.tex(300)
/root/usr/tomura(900)
/root/usr/tomura/game.doc(400)
/root/usr/tomura/junk.mail(500)
Composite 模式总结
组合模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。组合模式可以不提供父对象的管理方法,但组合模式必须在合适的地方提供子对象的管理方法(诸如:add、remove等)。安全式的组合模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件中。与安全式的组合模式不同的是,透明式的组合模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定的接口。
透明模式:也就是说在Component中声明所有用来管理子对象的方法,其中包括Add、Remove等。这样实现Component接口的所有子类都具备了Add和Remove。这样做的好处就是叶节点和枝节点对于外界没有区别,它们具有完全一致的行为接口,但问题也很明显,因为Leaf类本身不具备Add(),Remove()方法的功能,所以实现他是没有意义的。
安全模式:就是在Component接口中不去声明Add和Remove方法,那么子类的Leaf也就不需要去实现它,而是在Composite声明所有用来管理子类对象的方法,这样就不会出现透明模式出现的问题,不过由于不够透明,所以叶节点和枝节点将不具有相同的接口,客户端调用需要做相应的判断,带来了不便。
何时采用:
- 需求重要体现部分与整体的层次结构时
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
参考文献
- 《图解设计模式》
- 《设计模式之禅》
- 设计模式学习笔记十七:组合模式(Composite Pattern)
Builder模式定义
在建造大楼的时候,需要打牢地基,搭建框架,然后自下而上地一层一层盖起来。通常,在建造这种具有复杂结构的物体时,很难一气呵成。我们需要首先建造组成这个物体的各个部分,然后分阶段将它们组装起来。将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。就是Builder模式。
Builder模式代码实现
现在需要使用一段代码来编写文档,此处编写的文档有以下结构。
- 含有一个标题。
- 含有几个字符串。
- 含有条目项目。
Builder类定义了决定文档结构的方法,是一个抽象类。
public abstract class Builder {
public abstract void makeTitle(String title);
public abstract void makeString(String str);
public abstract void makeItems(String[] items);
public abstract void close();
}
然后Director类使用该方法编写一个具体的文档
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct(){
builder.makeTitle("Greeting");
builder.makeString("从早上至下午");
builder.makeItems(new String[]{
"早上好。",
"下午好。",
});
builder.makeString("晚上");
builder.makeItems(new String[]{
"晚上好。",
"晚安。",
"再见。",
});
builder.close();
}
}
之后会定义继承Builder类的TextBuilder类和HTMLBuilder类,将它们的实例作为参数传递给Director类,这些Builder类的子类决定了编写出的文档的形式。
public class TextBuilder extends Builder{
private StringBuffer buffer = new StringBuffer();
@Override
public void makeTitle(String title) {
buffer.append("=======================\n");
buffer.append("[" + title + "]\n");
buffer.append("\n");
}
@Override
public void makeString(String str) {
buffer.append("a" + str + "\n");
buffer.append("\n");
}
@Override
public void makeItems(String[] items) {
for(int i = 0;i < items.length; i++){
buffer.append(" `" + items[i] + "\n");
}
buffer.append("\n");
}
@Override
public void close() {
buffer.append("====================================\n");
}
public String getResult(){
return buffer.toString();
}
}
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class HTMLBuilder extends Builder{
private String filename;
private PrintWriter write;
@Override
public void makeTitle(String title) {
filename = title + ".html";
try {
write = new PrintWriter(new FileWriter(filename));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
write.println("<html><head><title>" + title + "</title></head><body>");
write.println("<h1>" + title + "</h1>");
}
@Override
public void makeString(String str) {
write.println("<p>" + str + "</p>");
}
@Override
public void makeItems(String[] items) {
write.println("<ul>");
for(int i = 0;i < items.length; i++){
write.println("<li>" + items[i] + "</li>");
}
write.println("</ul>");
}
@Override
public void close() {
write.println("</body></html>");
write.close();
}
public String getResult(){
return filename;
}
}
最后是测试代码
public class Main {
public static void main(String[] args) {
if(args.length != 1){
usage();
System.exit(0);
}
if(args[0].equals("plain")){
TextBuilder textBuilder = new TextBuilder();
Director director = new Director(textBuilder);
director.construct();
String result = textBuilder.getResult();
System.out.println(result);
} else if(args[0].equals("html")){
HTMLBuilder htmlBuilder = new HTMLBuilder();
Director director = new Director(htmlBuilder);
director.construct();
String filename = htmlBuilder.getResult();
System.out.println(filename + "文件编写完成。");
} else{
usage();
System.exit(0);
}
}
private static void usage() {
System.out.println("Usage : java Main plain 编写纯文本文档");
System.out.println("Usage : java Main html 编写HTML文档");
}
}
输入参数plain,会将TextBuilder类的实例作为参数传递至Director类的构造函数中。输出结果如下
=======================
[Greeting]
a从早上至下午
`早上好。
`下午好。
a晚上
`晚上好。
`晚安。
`再见。
====================================
Builder模式代优点
- 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。
- 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。
Builder模式代优点与Factory模式的区别
建造者模式仅仅只比工厂模式多了一个"导演类"的角色。在建造者模式的类图中,假如把这个导演类看做是最终调用的客户端,那么图中剩余的部分就可以看作是一个简单的工厂模式了。与工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂,因此将对象的创建过程独立出来组成一个新的类——导演类。也就是说,工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品;而建造者模式中,建造者类一般只提供产品类中各个组件的建造,而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品,然后将组建好的产品交付给客户端。
参考文献
- 《图解设计模式》
- 《设计模式之禅》
- 《极客学院——建造者模式》
Bridge模式定义
Bridge模式的作用是将两样东西连接起来,它们分别是类的功能层次结构和类的实现层次结构。桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
类的层次结构的两个作用
1. 希望增加新功能时
通常,我们希望在一个类中增加新功能时,会编写该类的子类,这样就构成了一个小小的类层次结构。这种层次结构就被称为“类的层次结构”。
- 父类具有基本功能。
- 在子类中增加新的功能。
2. 希望增加新的实现时
抽象类定义了接口,然后子类负责去实现这些抽象方法。父类的任务是通过声明抽象方法的方式定义接口,而子类的任务是实现抽象方法,这也是层次结构,但是这里的层次结构的真正作用是帮助实现任务分担,即: - 父类通过声明抽象方法来定义接口。
- 子类通过实现具体方法来实现接口。
Bridge模式代码实现
首先是“类的实现层次结构”的最上层。DisplayImpl类,是抽象类,声明了rawOpen、rawPrint、rawClose这三个抽象方法。
public abstract class DisplayImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}
定义类的功能层次结构Display类,它的功能是抽象的,负责显示东西。在impl字段保存的是实现Display类的具体功能的实例,该实例通过Display类的构造函数被传递给Display类,然后保存在impl字段中,以供后面的处理使用,impl字段即是类的两个层次结构的“桥梁”。
public class Display {
private DisplayImpl impl;
public Display(DisplayImpl impl) {
this.impl = impl;
}
public void open(){
impl.rawOpen();
}
public void print(){
impl.rawPrint();
}
public void close(){
impl.rawClose();
}
public final void display(){
open();
print();
close();
}
}
之后是“类的功能层次结构”CountDisplay类,继承了Display类,并在其基础上增加了一个新功能,原本只有现实功能,现在增加了只显示规定的次数的功能,就是multiDisplay方法。
public class CountDisplay extends Display{
public CountDisplay(DisplayImpl impl) {
super(impl);
}
public void multiDisplay(int times){
open();
for(int i = 0; i < times; i++){
print();
}
close();
}
}
最后是真正的实现——StringDisplayImpl类,是显示字符串的类,并不是直接地显示,而是继承了DisplayImpl类,作为其子类来时用rawOPen、rawPrint、rawClose方法进行显示。
import javax.print.event.PrintJobAttributeListener;
public class StringDisplayImpl extends DisplayImpl{
private String string;
private int width;
public StringDisplayImpl(String string){
this.string = string;
this.width = string.getBytes().length;
}
@Override
public void rawOpen() {
printLine();
}
private void printLine() {
System.out.print("+");
for(int i = 0;i < width;i++){
System.out.print("-");
}
System.out.println("+");
}
@Override
public void rawPrint() {
System.out.println("|" + string + "|");
}
@Override
public void rawClose() {
printLine();
}
}
测试类如下:
public class Main {
public static void main(String[] args) {
Display d1 = new Display(new StringDisplayImpl("Hello,CHina."));
Display d2 = new CountDisplay(new StringDisplayImpl("Hello,World."));
CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello,Universe."));
d1.display();
d2.display();
d3.display();
d3.multiDisplay(5);
RandomCountDisplay d = new RandomCountDisplay(new StringDisplayImpl("Hello,China."));
d.randomDisplay(10);
}
}
运行结果如下:
+------------+
|Hello,CHina.|
+------------+
+------------+
|Hello,World.|
+------------+
+---------------+
|Hello,Universe.|
+---------------+
+---------------+
|Hello,Universe.|
|Hello,Universe.|
|Hello,Universe.|
|Hello,Universe.|
|Hello,Universe.|
+---------------+
+------------+
|Hello,China.|
+------------+
参考文献
- 《图解设计模式》
- 《设计模式之禅》
- 《极客学院——桥接模式》
网友评论