设计模式总结(三)

Always on the way of programming.

作者: Ian | 2018-03-05 | 阅读



右侧可以看目录,点击直接跳转,这个系列一共写了4篇(可以点击下面直接跳转然后看右侧目录):

观察者模式

观察者模式: 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

还是先上代码,然后再说明:

// 抽象通知者
public abstract class Subject {
    private List<Observer> observers = new ArrayList<Observer>();

    //增加观察者
    public void attach(Observer observer){
        observers.add(observer);
    }

    //移除观察者
    public void detach(Observer observer){
        observers.remove(observer);
    }

    //通知
    public void notice(){
        for (Observer o : observers){
            o.update();
        }
    }
}
// 抽象观察者,为具体的观察者定义一个接口,在得到通知时更新自己。 
public abstract class Observer {
    public abstract void update();
}
// 具体通知者,将有关状态存入具体观察者对象;在具体通知者的内部状态改变时,给所有登记过的观察者发出通知。
public class ConcreteSubject extends Subject {
    private String subjectState;

    //具体被观察者状态
    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
    }
}
// 具体观察者类,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。
public class ConcreteObserver extends Observer{
    private String name;
    private String observerState;
    private ConcreteSubject subject;

    public ConcreteObserver(ConcreteSubject subject, String name) {
        this.subject = subject;
        this.name = name;
    }

    @Override
    public void update() {
        observerState = subject.getSubjectState();
        System.out.println("观察者"+ name + "的状态是:" + observerState);
    }

    public ConcreteSubject getSubject() {
        return subject;
    }

    public void setSubject(ConcreteSubject subject) {
        this.subject = subject;
    }
}
public class main {
    public static void main(String[] args){
        //实例化具体通知者
        ConcreteSubject s = new ConcreteSubject();
        s.attach(new ConcreteObserver(s,"x"));
        s.attach(new ConcreteObserver(s,"y"));
        s.attach(new ConcreteObserver(s,"Z"));

        s.setSubjectState("abc");
        s.notice();
    }
}

应用场景

  1. 当一个对象的改变需要同时改变其他对象的时候而且它不知道具体有多少对象有待改变时,应该考虑观察者模式。   2. 一个抽象模型有两个方便,其中一个方面依赖与另一个方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

总结

  观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖与抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。— 这个其实就是依赖倒转法则的完美体现。

观察者模式之事件委托

先上一个简单的例子,后面做详细说明:

// 通知者,可能是老板,可能是前台通知者
abstract class Notifier {
    protected Method update;
    abstract public void setAction(String action);
    abstract public void notice(Object object);
}
public class Boss extends Notifier {
    //通知者发现的状情况
    private String action;

    //老板发现的情况
    @Override
    public void setAction(String action) {
        this.action = action;
    }

    @Override
    public void notice(Object object) {
        if (update != null) {
            try {
                update.invoke(object, action);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

class GirlClassMate extends Notifier{
    private String action;         //通知者发现的情况
    public Method update;          //反射事件

    //秘书发现的情况
    @Override
    public void setAction(String action) {
        this.action = action;
    }

    @Override
    public void notice(Object object) {
        if (update != null) {
            try {
                update.invoke(object, action);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

public class StockObsrver {
    private String name;  //名字
    private Notifier notifier; //卧底

    public StockObsrver(String name, Notifier notifier) {
        this.name = name;
        this.notifier = notifier;
    }

    //关闭股票行情
    public void closeStockMarket(String action) {
        System.out.println(action + name + "关闭股票详情,请继续工作!");
    }
}

class NBAObserver {
    private String name;
    private Notifier notifier;

    public NBAObserver(String name, Notifier notifier) {
        this.name = name;
        this.notifier = notifier;
    }

    //关闭NBA直播
    public void closeNBADirectSeeding(String action) {
        System.out.println(action + name + "关闭直播,请继续工作!");
    }
}
public class main {
    public static void main(String[] args){
        Boss huaji = new Boss();

        //看股票的同事
        StockObsrver colleague = new StockObsrver("赵六",huaji);

        //看NBA的同事
        NBAObserver workmate = new NBAObserver("王五",huaji);

        //BOSS 回来了
        huaji.setAction("我回来了!");

        try {
            huaji.update = colleague.getClass().getMethod("closeStockMarket",new Class[] {String.class});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        //发出通知
        huaji.notice(colleague);

        try {
            huaji.update = workmate.getClass().getMethod("closeNBADirectSeeding",new Class[]{String.class});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        huaji.notice(workmate);

    }
}

  说明:委托是一种饮用方法的类型,一旦为委托分配了方法,委托将于该方法具有完全相同的行为。委托方法的使用可以像其它任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。
  注意:委托对象所搭载的所有方法必须具有相同的原型和形式,也就是拥有相同的参数列表和返回值类型。是先有观察者模式,再有委托事件技术的。
   ####观察者模式–事件委托(具体的在注释里面):

  1. C# :
    声明一个事件Update,类型为EventHandler; event是一个事件,比如:按键、点击、鼠标移动等等; public event EventHandler Update;

  2. java :

总结:写出这种代码的又两种情况:1.自己用过EventHandler; 2. alt + 1 提示修改测试修改再测试调试出来的;
C# 中的 EventHandler 和 Java 中的 EventHandler 是完全不一样的意思,我看网上面那些例子…..

抽象工厂模式

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

继续先上代码:

public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Department {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
// 用于客户端访问,解除与具体数据库访问的耦合
public interface IUser {
    void inserts(User user);

    User getUser(int id);
}
public interface IDepartment {
    void inserts(Department department);
    Department getDepartment(int id);
}
// 定义一个创建访问Department表对象的抽象的工厂接口。
public interface IFactory {
    IUser createUser();
    IDepartment createDepartment();
}
public class AccessUser implements IUser {
    @Override
    public void inserts(User user) {
        System.out.println("在Access中给Department增加一条数据。");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在Access中根据id得到department表一条数据。");
        return null;
    }
}
// 实例化AccessUser和AccessDepartment
public class AccessFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new AccessUser();
    }

    //增加了AccessDepartment工厂
    @Override
    public IDepartment createDepartment() {
        return new AccessDepartment();
    }
}
public class AccessDepartment implements IDepartment {
    @Override
    public void inserts(Department department) {
        System.out.println("在Access中给Department增加一条数据。");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在Access中根据id得到department表一条数据。");
        return null;
    }
}
// 用与访问数据库的Department
public class SqlserverDepartment implements IDepartment {
    @Override
    public void inserts(Department department) {
        System.out.println("在数据库department表中增加一条数据。");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在数据库中根据id得到department表一条数据。");
        return null;
    }
}
// 实例化SqlserverUser和SqlserverDepartment
public class SqlServerFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new SqlserverUser();
    }

    //增加SqlserverDepartment工厂
    @Override
    public IDepartment createDepartment() {
        return new SqlserverDepartment();
    }
}
public class SqlserverUser implements IUser {
    @Override
    public void inserts(User user) {
        System.out.println("在数据库department表中增加一条数据。");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在数据库中根据id得到department表一条数据。");
        return null;
    }
}
public class main {
    public static void main(String[] args){
        User user = new User();  //用户表
        Department dept = new Department(); //部门表

        IFactory factory = new AccessFactory();
        IUser iu = factory.createUser();   //此时已与具体数据库访问解除了依赖
        iu.inserts(user);
        iu.getUser(1);

        IDepartment id = factory.createDepartment();  //此时已与具体数据库访问解除了依赖
        id.inserts(dept);
        id.getDepartment(1);

        //实例化哪个数据库  这里同上
        IFactory iFactory = new SqlServerFactory();
        IDepartment i = iFactory.createDepartment();
        i.inserts(dept);
        i.getDepartment(1);

        IUser user1 = iFactory.createUser();
        user1.inserts(user);
        user1.getUser(1);
    }
}

   如果没看懂代码的意思建议多看几遍。

优点:

   易于交换产品系列,由于具体工厂类,例如:IFactory factory = new AccessFactroy();在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。

缺点:

   客户端程序类显然不会是只有一个,有很多地方都在使用IUser或者IDepartment,而这样的设计,其实在每一个类的开始都需要声明IFactory factory = new SqlserverFactory();如果我有100个调用数据库数据库访问的类,是不是就要更改100次…   

状态模式

状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

和往常一样,先上代码:

// 抽象状态类,定义一个接口以封装与Context的一个特定状态相关行为。
public abstract class State {
    public abstract void handle(Context context);
}
// 维护一个ConcreteState子类的实例,这个实例定义当前的状态
public class Context {
    private State state;

    //定义Context的初始状态
    public Context(State state){
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
        System.out.println("当前状态:"+state.getClass().getTypeName());
    }

    // 对请求做处理,并设置下一个状态
    public void request(){
        state.handle(this);
    }
}
// 具体状态,每一个子类实现一个与Context的一个状态相关的行为。
public class ConcreteStateA extends State {
    // 设置ConcreteStateA的下一个状态是ConcreteStateB
    @Override
    public void handle(Context context) {
        context.setState(new ConcreteStateB());
    }
}

class ConcreteStateB extends State{
    // 设置ConcreteStateB的下一个状态是ConcreteStateA
    @Override
    public void handle(Context context) {
        context.setState(new ConcreteStateA());
    }
}
public class main {
    public static void main(String[] args) {
        //设置Context的初始状态为ConcreteStateA
        Context c = new Context(new ConcreteStateA());
        // 不断请求,同时更改状态
        c.request();
        c.request();
        c.request();
        c.request();
    }
}

好处:

  是将于特定状态相关的行为局部化,并且将不同状态的行为分割开来。–说白了目的就是消除庞大的条件分支语句,状态模式通过把各种状态转移逻辑分布到state的子类之间,来减少相互的依赖,此时就容易维护和扩展了。

应用场景:  

  当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了。   

适配器模式

适配器模式:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

例子:

// 这是客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口。
public class Target {
    public void request(){
        System.out.println("普通请求!");
    }
}
// 需要适配的类。
public class Adpatee {
    public void specificRequest(){
        System.out.println("特殊请求!");
    }
}
// 通过在内部包装一个Adaptee对象,把源接口转换成目标接口。
public class Adapter extends Target {
    // 简历一个私有的Adpatee
    private Adpatee adapter = new Adpatee();

    // 这样就可以把表面上调用request()方法变成实际调用specificRequest();
    @Override
    public void request(){
        adapter.specificRequest();
    }
}
public class main {
    public static void main(String[] args){
        Target target = new Adapter();
        // 对客户端来说,调用的就是Target的request()
        target.request();
    }
}

应用场景

  适配器模式主要应用于希望复用一些现存的类,但是接口又于复用环境要求不一致的情况。要在双方都不太容易修改的时候再使用适配器模式适配。

总结:

  就是需要的东西就在面前,但却不能使用,而短时间又无法改造它,于是我们就想办法适配它。

备忘录模式

备忘录:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

例子:

public class Originator {
    // 需要保存的属性,可能有多个
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    // 创建备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
    public Memento createMemento(){
        return new Memento(state);
    }

    // 恢复备忘录,将Memento 导入并将相关数据恢复。
    public void setMemento(Memento memento){
        state = memento.getState();
    }

    // 显示数据
    public void show(){
        System.out.println("state = " + state);
    }

}
public class Memento {
    private String state;

    // 构造方法将相关数据导入
    public Memento(String state){
        this.state = state;
    }

    // 需要保存的数据属性,可以是多个
    public String getState() {
        return state;
    }
}
public class Caretaker {
    private Memento memento;

    // 得到备忘录
    public Memento getMemento() {
        return memento;
    }

    // 设置备忘录
    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
public class main {
    public static void main(String[] args){
        Originator o = new Originator();
        o.setState("on");  // 设置初始状态,状态属性为"on"

        // 保存状态时,由于有了很好的封装,可以隐藏Originator的实现细节。
        Caretaker c = new Caretaker();
        c.setMemento(o.createMemento());

        // 改变状态属性 off
        o.setState("off");
        o.show();

        // 恢复原初始状态
        o.setMemento(c.getMemento());
        o.show();
    }
}

  上面例子中是把要保存的细节给封装在了Memento中,哪一天要更改保存的细节也不会影响客户端。

应用场景:

  Memento 模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分,Originator可以根据保存的Memento信息还原到前一状态。   

组合模式

组合模式:将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

例子:

// 为组合中的对象声明接口,在适当的情况下,实现所有共有接口的默认行为。声明一个接口用于访问和管理Component的子部件。
public abstract class Component {
    protected String name;

    public Component(String name){
        this.name = name;
    }

    // 用 append 和 remove 方法来提供增加或移除功能
    public abstract void append(Component c);
    public abstract void remove(Component c);
    public abstract void display(int depth);

}
// leaf 在组合中表示叶节点对象,叶节点没有子节点。
public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    // 由于叶子没有增加分支和树叶,所有apend 和 remove 方法实现它没有意义,但这样做可以消除叶节点对象在抽象层次的区别,它们具备完全一致的接口。
    @Override
    public void append(Component c) {
        System.out.println("Cannot add to a leaf");
    }

    @Override
    public void remove(Component c) {
        System.out.println("Cannot remove from a leaf");
    }

    // 叶节点的具体方法,此处是显示其名称和级别
    @Override
    public void display(int depth) {
        System.out.println(depth + "-" + name);
    }
}
// 定义有枝节点行为,用来存储子部件,在Component 接口中实现与子部件有关的操作,比如 增加删除。
public class Composite extends Component {

    // 一个子对象集合用来存储其下属的枝节点和叶节点。
    private List<Component> chideren = new ArrayList<Component>();

    public Composite(String name) {
        super(name);
    }

    @Override
    public void append(Component c) {
        chideren.add(c);
    }

    @Override
    public void remove(Component c) {
        chideren.remove(c);
    }

    // 显示其枝节点名称,并对其下级进行遍历
    @Override
    public void display(int depth) {
        System.out.println(depth + "-" + name);
        for (Component component : chideren){
            component.display(depth + 1);
        }
    }
}
public class main {
    public static void main(String[] args){
        // 生成树根root,根上长出两叶LeafA 和 LeafB
        Composite root = new Composite("root");
        root.append(new Leaf("LeafA"));
        root.append(new Leaf("LeafB"));

        // 根上长出分枝Composite X,分支上也有两叶Leaf XA 和 Leaf XB
        Composite comp = new Composite("Composite X");
        comp.append(new Leaf("Leaf XA"));
        comp.append(new Leaf("Leaf XB"));

        root.append(comp);

        // 在Commposite X 上再长出分支 Composite XY,分支上也有两叶 LeafXYA 和 LeafXYB
        Composite comp2 = new Composite("XY");
        comp2.append(new Leaf("Leaf XYA"));
        comp2.append(new Leaf("Leaf XYB"));

        comp.append(comp2);

        root.append(new Leaf("Leaf C"));

        // 根部又长出两叶Leaf C 和 Leaf D,可惜Leaf D 没长好就死掉来。
        Leaf leaf = new Leaf("Leaf D");
        root.append(leaf);
        root.remove(leaf);

        // 显示大树的样子
        root.display(1);
    }
}

透明方式:

  也就是说在Component中声明所有用来管理子对象的方法,其中包括append、remove等。这样实现Component接口所有子类都具备了append 和 remove。这样做的好处就是叶节点和枝节点对于外界没有区别,它们具备完全一致的行为接口。但问题也很明显,因为Leaf类本身不具备append()、remove()方法的功能,所以实现它是没有意义的。   

安全方式:

  也就是在Component接口中不去声明appeng 和 remove方法,那么子类Leaf 也就不需要去实现它,而是在Composite 声明所有用来管理子类对象的方法,这样做就不会出现刚才提到的问题,不过由于不够透明,所以树叶和树枝将不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。
  一般情况的话,都是使用透明模式,那样就不用做任何判断了。   

应用场景:

  当你发现需求中是体现部分与整体层次的机构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。   

优点:

  组合模式让客户可以一致地使用组合结构和单个对象。   

迭代器模式

迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

应用场景:

  当你需要访问一个聚集对象,而不管这些对象是什么都需要遍历的时候,你就应该考虑用迭代器模式。

说明:

  现在java 里面都封装好了,Iterator 遍历Map、List集合经常用到,这里我就不多说了,百度很多的。
  

右侧可以看目录,点击直接跳转,这个系列一共写了4篇(可以点击下面直接跳转然后看右侧目录):


结语

  每个人吸收知识的时候,都要有抽取精华,去除糟粕的能力。作者所说的,可能有些是对的,有些是错的,有些是适合你的,有些是不太适合你的,你要自己能够判断。

其实你在生活和工作当中也是一样的,你身边的人形形色色,有的人你喜欢,有的人你很讨厌。但其实你喜欢的人也有缺点,你讨厌的人也有优点。你要学会从你讨厌的人身上学会他的优点,千万不要一棒子打死,这只会让你失去很多学习成长的机会。

希望本文可以帮助到作为程序猿或即将成为程序猿的你。


版权声明:本文由 Ian 在 2018年03月05日发表。本文采用CC BY-NC-SA 4.0许可协议,非商业转载请注明出处,不得用于商业目的。
文章题目及链接:《设计模式总结(三)》




  相关文章:


留言区:

TOP