Android 设计模式

设计模式共23

模板方法(Template Method)模式

模板方法: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

Java设计模式透析之 —— 模板方法(Template Method)

设计模式 模版方法模式 展现程序员的一天

首先将Formatter中的代码进行修改,如下所示:

public abstract class Formatter {  
  
    public String formatBook(Book book, int format) {  
        beforeFormat();  
        String result = formating(book);  
        afterFormat();  
        return result;  
    }  
  
    protected void beforeFormat() {  
        System.out.println("format begins");  
    }  
  
    protected abstract String formating(Book book);  
  
    protected void afterFormat() {  
        System.out.println("format finished");  
    }  
  
}  

然后要定义专门的子类来处理每种传输格式的具体逻辑,这样不同传输格式的逻辑可以从一个方法里分离开,明显便于阅读和理解。

定义类XMLFormatter继承自Formatter,里面加入处理XML格式的具体逻辑:

public class XMLFormatter extends Formatter {  
  
    @Override  
    protected String formating(Book book) {  
        String result = "";  
        result += "<book_name>" + book.getBookName() + "</book_name>\n";  
        result += "<pages>" + book.getPages() + "</pages>\n";  
        result += "<price>" + book.getPrice() + "</price>\n";  
        result += "<author>" + book.getAuthor() + "</author>\n";  
        result += "<isbn>" + book.getIsbn() + "</isbn>\n";  
        return result;  
    }  
  
} 

定义类JSONFormatter继承自Formatter,里面加入处理JSON格式的具体逻辑:

public class JSONFormatter extends Formatter {  
  
    @Override  
    protected String formating(Book book) {  
        String result = "";  
        result += "{\n";  
        result += "\"book_name\" : \"" + book.getBookName() + "\",\n";  
        result += "\"pages\" : \"" + book.getPages() + "\",\n";  
        result += "\"price\" : \"" + book.getPrice() + "\",\n";  
        result += "\"author\" : \"" + book.getAuthor() + "\",\n";  
        result += "\"isbn\" : \"" + book.getIsbn() + "\",\n";  
        result += "}";  
        return result;  
    }  
  
}  

最后调用代码如下:

public class Test {  
  
    public static void main(String[] args) throws Exception {  
        Book book = new Book();  
        book.setBookName("Thinking in Java");  
        book.setPages(880);  
        book.setPrice(68);  
        book.setAuthor("Bruce Eckel");  
        book.setIsbn("9787111213826");  
        XMLFormatter xmlFormatter = new XMLFormatter();  
        String result = xmlFormatter.formatBook(book);  
        System.out.println(result);  
        JSONFormatter jsonFormatter = new JSONFormatter();  
        result = jsonFormatter.formatBook(book);  
        System.out.println(result);  
    }  
  
}  

运行之后,你会发现运行结果和修改前代码的运行结果完全相同。但是使用模板方法之后,代码的可读性有了很大的提高,因为处理格式转换的代码都放到了各自的类当中,而不是全部塞进一个方法中。并且在扩展性上也有了很大的提升,比如你开始感兴趣项目经理说的YAML格式了。

定义类YAMLFormatter继承自Formatter,里面加入处理YAML格式的具体逻辑:

public class YAMLFormatter extends Formatter {  
  
    @Override  
    protected String formating(Book book) {  
        String result = "";  
        result += "book_name: " + book.getBookName() + "\n";  
        result += "pages: " + book.getPages() + "\n";  
        result += "price: " + book.getPrice() + "\n";  
        result += "author: " + book.getAuthor() + "\n";  
        result += "isbn: " + book.getIsbn() + "\n";  
        return result;  
    }  
  
}  

调用代码只需要加入:

YAMLFormatter yamlFormatter = new YAMLFormatter();  
String result = yamlFormatter.formatBook(book);  
System.out.println(result);  

好了,令人头疼的YAML格式就这样被支持了,只需要在调用的时候决定是实例化XMLFormatterJSONFormatter还是YAMLFormatter,就可以按照相应的规格进行格式转换了。而且整体的代码很有条理,看起来也很舒心。这个时候,你会轻松地向项目经理调侃一句,还有需要支持的格式吗?

单例(Singleton)模式

单例:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 

继续设计模式,这个模式用得应该很频繁啊,而且也比较简单,如果现在你还不能纸笔随手写个单例出来,你就得加油了哈~

直接介绍几种线程安全的且我觉得还比较不错的方式: 

1、饿汉,就是类加载就初始化了

public class Singleton {
    private static Singleton instance = new Singleton();
private Singleton (){   /* 私有构造方法,防止被实例化 */    
    }
    public static Singleton getInstance(){
        return instace;
    }
}

饿汉模式是一种典型的以空间换时间的做法,当类装载时就会创建类的实例,在未引用实例之前将实例对象创建出来,节省了时间。但是,过早的实例化对象,存在浪费内存的说法。由于classloder机制,保证当一个类被加载的时候,这个类的加载是线程互斥的,而饿汉单例的静态instance直接新建一个实例,在加载的时候就能线程安全的获得实例。从而避免了线程安全问题。 

优点:写法简单、良好地支持多线程且运行效率很高。

缺点:不具备懒加载,不能预防反序列化,过早实例化对象浪费内存。

2、懒汉,需要双重判断 double-check(DCL)

Java设计模式透析之 —— 单例(Singleton)(必看)

设计模式-单例模式

public class Singleton {  
  
    private volatile static Singleton sSingleton;  
  
    private Singleton() {  
    }  
  
    public static Singleton getInstance() {  
        if (sSingleton == null) { // line A  
            synchronized (Singleton.class) { // line C  
                if (sSingleton == null)  
                sSingleton = new Singleton();  // line B  
            }  
        }  
  
        return sSingleton;  
    }  
  
}  

上述代码近乎完美,可以满足几乎所有场合(采用反射和类加载器另当别论)。上述代码的好处在于:第一次创建实例的时候会同步所有线程,以后有线程再想获取Singleton的实例就不需要进行同步,直接返回实例即可。

还有double-check的意义在于:假设现在有2个线程AB同时进入了getInstance方法,线程A执行到line A行,线程B执行到line B行,由于B线程还没有初始化完毕,sSingleton还是null,于是线程A通过了sSingleton==null的判断,并且往下执行,碰巧,当线程A执行到line C的时候,线程B初始化完毕了,然后线程B返回,注意,如果没有double-check,这个时候线程A就执行到了line B,就会再次初始化sSingleton,这个时候Singleton实际上被new了两次,已经不算完全意义上的单例了,而有了double-check,就会再进行一次为null的判断,由于B线程已经初始化了sSingleton,所以A线程就不会再次初始化sSingleton

3、使用Java的枚举,还是很推荐的,简单的跟神马一样

java枚举使用详解

public enum SingletonEnum {  
      
     instance;   
       
     private SingletonEnum() {}  
       
     public void method(){  
     }  
}  

访问方式:

SingletonEnum.instance.method();  

可以看到枚举的书写非常简单,访问也很简单在这里SingletonEnum.instance这里的instance即为SingletonEnum类型的引用所以得到它就可以调用枚举中的方法了。

借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过,这种方式也是最好的一种方式,如果在开发中JDK满足要求的情况下建议使用这种方式。

4、静态内部类单例模式

public class Singleton{  
    private Singleton(){  
    }  
  
    private static class InstanceHolder{  
        private static final Singleton instance = new Singleton();  
    }  
  
    public static Singleton getInstance(){  
        return InstanceHolder.instance;  
    }  
  
} 

就目前来看,DCL静态内部类单例模式高并发场合首选的单例实现方式,在一些对并发要求不高的场合,我们也可以采用其他简单的写法,要做到具体情况具体分析,选择适合的单例模式也是很有必要的,而不是一味地去追求高并发。

好了,就这么多,以上4种都是比较推荐使用的,除了第一种会类加载的时候初始化,其他3中都不会,且4种都保证线程安全,特殊情况(除了多个类加载器,和你非要通过反射等手段生成多个对象)不考虑。

优点与缺点

优点

1、由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。

2、由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决;

3、单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

4、单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

缺点 :

1、单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。

2、单例模式与单一职责原则有冲突。

策略(Strategy)模式

Java设计模式透析之 —— 策略(Strategy)

策略:它定义了算法家庭,分别封装起来。让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户 

首先定义一个策略接口:

public interface Strategy {  
  
    String getSQL(String[] usernames);    
} 

然后定义两个子类都实现了上述接口,并将两种拼装SQL语句的算法分别加入两个子类中:

public class Strategy1 implements Strategy {  
  
    @Override  
    public String getSQL(String[] usernames) {  
        StringBuilder sql = new StringBuilder("select * from user_info where ");  
        for (String user : usernames) {  
            sql.append("username = '");  
            sql.append(user);  
            sql.append("' or ");  
        }  
        sql.delete(sql.length() - " or ".length(), sql.length());  
        return sql.toString();  
    }  
  
}  
public class Strategy2 implements Strategy {  
  
    @Override  
    public String getSQL(String[] usernames) {  
        StringBuilder sql = new StringBuilder("select * from user_info where ");  
        boolean needOr = false;  
        for (String user : usernames) {  
            if (needOr) {  
                sql.append(" or ");  
            }  
            sql.append("username = '");  
            sql.append(user);  
            sql.append("'");  
            needOr = true;  
        }  
        return sql.toString();  
    }  
  
}  

然后把QueryUtilfindUserInfo方法的第二个参数改成Strategy对象,这样只需要调用StrategygetSQL方法就可以获得拼装好的SQL语句,代码如下所示:

public class QueryUtil {  
  
    public void findUserInfo(String[] usernames, Strategy strategy) throws Exception {  
        Class.forName("com.mysql.jdbc.Driver");  
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root",  
                "123456");  
        Statement stat = conn.createStatement();  
        String sql = strategy.getSQL(usernames);  
        System.out.println(sql);  
        ResultSet resultSet = stat.executeQuery(sql);  
        while (resultSet.next()) {  
            // 处理从数据库读出来的数据  
        }  
        // 后面应将读到的数据组装成对象返回,这里略去。  
    }  
}  

最后,测试代码在调用findUserInfo方法时,只需要显示地指明需要使用哪一个策略对象就可以了:

public class Test {  
  
public static void main(String[] args) throws Exception {  
    QueryUtil query = new QueryUtil();  
     query.findUserInfo(new String[] { "Tom", "Jim", "Anna" }, new Strategy1());  
     query.findUserInfo(new String[] { "Jac", "Joe", "Rose" }, new Strategy2());  
  }  
  
}  

打印出的SQL语句丝毫不出预料,如下所示:

select * from user_info where username = 'Tom' or username = 'Jim' or username = 'Anna'  
select * from user_info where username = 'Jac' or username = 'Joe' or username = 'Rose'  

使用策略模式修改之后,代码的可读性和扩展性都有了很大的提高,即使以后还需要添加新的算法,你也是手到擒来了!

策略模式:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

先不管定义是如何,让我们先来看一个例子。假设你要设计一个游戏里的人物(包括玩家、NPC),如何进行设计呢?我们知道,一个游戏人物肯定有基本属性、动作(如血量的属性、行走的动作),能够拿不同的武器,能够换不同的装备......如何进行弹性的设计。在这里,我们就可以使用策略模式。

设计原则1:找出应用之中可能变化之处,把它们独立出来,不要和那些不要变化的代码混在一起。在这里,我们可以看到游戏人物可以拿不同的武器, 能够换不同的装备,属于变化的部分,可以独立出来;行走的动作等属于不变的部分,可以不需独立出来,可以先实现为一个类。



基本人物代码:

现在我们考虑的是如何实现装备、武器等的实现,使其能够具有弹性,在游戏时动态改变。所以,就需要设计原则2:针对接口编程,而不是针对实现编程。利用接口表示每个行为,比如EquipmentInterface,WeaponInterface.我们不用GamePerson实现这两个接口,而是自己创建具体的装备类、武器类实现各自的接口。如:ClothingEquipmentWristEquipment,SwordWeapton,GunWeapton.

具体代码如下:


装备接口:


衣服装备:


手腕装备:


武器接口:


刀剑武器:


枪武器:


好,最后我们就需要将这两个组合在一起了。具体的说,需要在游戏人物中加入WeaptonInterfaceEquipmentInterface。如下:


测试一下:


在最后需要记住设计原则3:多用组合,少用继承。

组合(Composite)模式

Java设计模式透析之 —— 组合(Composite)

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



(Adapter)模式

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

需要值得注意的一点是,适配器模式不并是那种会让架构变得更合理的模式,更多的时候它只是充当救火队员的角色,帮助解决由于前期架构设计不合理导致的接口不匹配的问题。更好的做法是在设计的时候就尽量把以后可能出现的情况多考虑一些,在这个问题上不要向你的leader学习。

今天一大早,你的leader就匆匆忙忙跑过来找到你:快,快,紧急任务!最近ChinaJoy马上就要开始了,老板要求提供一种直观的方式,可以查看到我们新上线的游戏中每个服的在线人数。

你看了看日期,不是吧!这哪里是马上要开始了,分明是已经开始了!这怎么可能来得及呢?

没关系的。你的leader安慰你道:功能其实很简单的,接口都已经提供好了,你只需要调用一下就行了。

好吧,你勉为其难地接受了,对于这种突如其来的新需求,你早已习惯。

你的leader向你具体描述了一下需求,你们的游戏目前有三个服,一服已经开放一段时间了,二服和三服都是新开的服。设计的接口非常轻便,你只需要调用Utility.getOnlinePlayerCount(int),传入每个服对应的数值就可以获取到相应服在线玩家的数量了,如一服传入1,二服传入2,三服则传入3。如果你传入了一个不存在的服,则会返回-1。然后你只要将得到的数据拼装成XML就好,具体的显示功能由你的leader来完成。

好吧,听起来功能并不是很复杂,如果现在就开始动工好像还来得及,于是你马上敲起了代码。

首先定义一个用于统计在线人数的接口PlayerCount,代码如下:

public interface PlayerCount {  
  
    String getServerName();  
  
    int getPlayerCount();  
  
} 

接着定义三个统计类实现了PlayerCount接口,分别对应了三个不同的服,如下所示:

public class ServerOne implements PlayerCount {  
  
    @Override  
    public String getServerName() {  
        return "一服";  
    }  
  
    @Override  
    public int getPlayerCount() {  
        return Utility.getOnlinePlayerCount(1);  
    }  
  
}  
public class ServerTwo implements PlayerCount {  
  
    @Override  
    public String getServerName() {  
        return "二服";  
    }  
  
    @Override  
    public int getPlayerCount() {  
        return Utility.getOnlinePlayerCount(2);  
    }  
  
}  
public class ServerThree implements PlayerCount {  
  
    @Override  
    public String getServerName() {  
        return "三服";  
    }  
  
    @Override  
    public int getPlayerCount() {  
        return Utility.getOnlinePlayerCount(3);  
    }  
  
}  

然后定义一个XMLBuilder类,用于将各服的数据封装成XML格式,代码如下:

public class XMLBuilder {  
  
public static String buildXML(PlayerCount player) {  
        StringBuilder builder = new StringBuilder();  
        builder.append("<root>");  
        builder.append("<server>").append(player.getServerName()).append("</server>");  
        builder.append("<player_count").append(player.getPlayerCount()).append("</player_count>");  
        builder.append("</root>");  
        return builder.toString();  
    }  
  
} 

这样的话,所有代码就完工了,如果你想查看一服在线玩家数只需要调用:

XMLBuilder.buildXML(new ServerOne()); 

查看二服在线玩家数只需要调用:

XMLBuilder.buildXML(new ServerTwo()); 

查看三服在线玩家数只需要调用:

XMLBuilder.buildXML(new ServerThree());  

咦?你发现查看一服在线玩家数的时候,返回值永远是-1,查看二服和三服都很正常。

你只好把你的leader叫了过来:我感觉我写的代码没有问题,但是查询一服在线玩家数总是返回-1,为什么会这样呢?

哎呀!你的leader猛然想起,这是我的问题,前面没跟你解释清楚。由于我们的一服已经开放一段时间了,查询在线玩家数量的功能早就有了,使用的是ServerFirst这个类。当时写Utility.getOnlinePlayerCount()这个方法主要是为了针对新开的二服和三服,就没把一服的查询功能再重复做一遍。

听到你的leader这么说,你顿时松了一口气:那你修改一下Utility.getOnlinePlayerCount()就好了,应该没我什么事了吧?

晤。。。本来应该是这样的。。。可是,UtilityServerFirst这两个类都已经被打到Jar包里了,没法修改啊。。。你的leader有些为难。

什么?这不是坑爹吗,难道要我把接口给改了?你已经泪流满面了。

这倒不用,这种情况下可以使用适配器模式,这个模式就是为了解决接口之间不兼容的问题而出现的。

其实适配器模式的使用非常简单,核心思想就是只要能让两个互不兼容的接口能正常对接就行了。上面的代码中,XMLBuilder中使用PlayerCount这个接口来拼装XML,而ServerFirst并没有实现PlayerCount这个接口,这个时候就需要一个适配器类来为XMLBuilderServerFirst之间搭起一座桥梁,毫无疑问,ServerOne就将充当适配器类的角色。修改ServerOne的代码,如下所示:

public class ServerOne implements PlayerCount {  
      
    private ServerFirst mServerFirst;  
      
    public ServerOne() {  
        mServerFirst = new ServerFirst();  
    }  
  
    @Override  
    public String getServerName() {  
        return "一服";  
    }  
  
    @Override  
    public int getPlayerCount() {  
        return mServerFirst.getOnlinePlayerCount();  
    }  
  
}  

这样通过ServerOne的适配,XMLBuilderServerFirst之间就成功完成对接了!使用的时候我们甚至无需知道有ServerFirst这个类,只需要正常创建ServerOne的实例就行了。

观察者模式:

设计模式 观察者模式 以微信公众服务为例

Java中的观察者模式

http://droidyue.com/blog/2015/06/27/desgign-pattern-observer/

观察者模式的定义:

定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。

一个 observable 对象可以有一个或多个观察者。观察者可以是实现了 Observer 接口的任意对象。一个 observable 实例改变后,调用 Observable notifyObservers 方法的应用程序会通过调用观察者的 update 方法来通知观察者该实例发生了改变。 

未指定发送通知的顺序。Observable 类中所提供的默认实现将按照其注册的重要性顺序来通知 Observers,但是子类可能改变此顺序,从而使用非固定顺序在单独的线程上发送通知,或者也可能保证其子类遵从其所选择的顺序。

好了,对于定义的理解总是需要实例来解析的,如今的微信服务号相当火啊,下面就以微信服务号为背景,给大家介绍观察者模式。

看一张图:

其中每个使用者都有上图中的3条线,为了使图片清晰省略了。

如上图所示,服务号就是我们的主题,使用者就是观察者。现在我们明确下功能:

1、服务号就是主题,业务就是推送消息

2、观察者只需要订阅主题,只要有新的消息就会送来

3、当不想要此主题消息时,取消订阅

4、只要服务号还在,就会一直有人订阅

好了,现在我们来看看观察者模式的类图:




接下来就是代码时间了,我们模拟一个微信3D彩票服务号,和一些订阅者。

首先开始写我们的主题接口,和观察者接口:

package com.zhy.pattern.observer;  
  
/** 
 * 主题接口,所有的主题必须实现此接口 
 *  
 * @author zhy 
 *  
 */  
public interface Subject  
{  
    /** 
     * 注册一个观察着 
     *  
     * @param observer 
     */  
    public void registerObserver(Observer observer);  
  
    /** 
     * 移除一个观察者 
     *  
     * @param observer 
     */  
    public void removeObserver(Observer observer);  
  
    /** 
     * 通知所有的观察着 
     */  
    public void notifyObservers();  
  
}  


package com.zhy.pattern.observer;  
  
/** 
 * @author zhy 所有的观察者需要实现此接口 
 */  
public interface Observer  
{  
    public void update(String msg);  
  
} 

接下来3D服务号的实现类:

package com.zhy.pattern.observer;  
  
import java.util.ArrayList;  
import java.util.List;  
  
public class ObjectFor3D implements Subject  
{  
    private List<Observer> observers = new ArrayList<Observer>();  
    /** 
     * 3D彩票的号码 
     */  
    private String msg;  
  
    @Override  
    public void registerObserver(Observer observer)  
    {  
        observers.add(observer);  
    }  
  
    @Override  
    public void removeObserver(Observer observer)  
    {  
        int index = observers.indexOf(observer);  
        if (index >= 0)  
        {  
            observers.remove(index);  
        }  
    }  
  
    @Override  
    public void notifyObservers()  
    {  
        for (Observer observer : observers)  
        {  
            observer.update(msg);  
        }  
    }  
  
    /** 
     * 主题更新消息 
     *  
     * @param msg 
     */  
    public void setMsg(String msg)  
    {  
        this.msg = msg;  
          
        notifyObservers();  
    }    
} 

模拟两个使用者:

package com.zhy.pattern.observer;  
  
public class Observer1 implements Observer  
{  
  
    private Subject subject;  
  
    public Observer1(Subject subject)  
    {  
        this.subject = subject;  
        subject.registerObserver(this);  
    }  
  
    @Override  
    public void update(String msg)  
    {  
        System.out.println("observer1 得到 3D 号码  -->" + msg + ", 我要记下来。");  
    }  
  
}  

package com.zhy.pattern.observer;  
  
public class Observer2 implements Observer  
{  
    private Subject subject ;   
      
    public Observer2(Subject subject)  
    {  
        this.subject = subject  ;  
        subject.registerObserver(this);  
    }  
      
    @Override  
    public void update(String msg)  
    {  
        System.out.println("observer2 得到 3D 号码 -->" + msg + "我要告诉舍友们。");  
    }  
      
      
  
}  

可以看出:服务号中维护了所有向它订阅消息的使用者,当服务号有新消息时,通知所有的使用者。整个架构是一种松耦合,主题的实现不依赖与使用者,当增加新的使用者时,主题的代码不需要改变;使用者如何处理得到的数据与主题无关;

松耦合

1. 观察者增加或删除无需修改主题的代码,只需调用主题对应的增加或者删除的方法即可。

2. 主题只负责通知观察者,但无需了解观察者如何处理通知。举个例子,送奶站只负责送递牛奶,不关心客户是喝掉还是洗脸。

3. 观察者只需等待主题通知,无需观察主题相关的细节。还是那个例子,客户只需关心送奶站送到牛奶,不关心牛奶由哪个快递人员,使用何种交通工具送达。

通知不错过

由于被动接受,正常情况下不会错过主题的改变通知。而主动获取的话,由于时机选取问题,可能导致错过某些状态。

最后看下测试代码:

package com.zhy.pattern.observer.test;  
  
import com.zhy.pattern.observer.ObjectFor3D;  
import com.zhy.pattern.observer.Observer;  
import com.zhy.pattern.observer.Observer1;  
import com.zhy.pattern.observer.Observer2;  
import com.zhy.pattern.observer.Subject;  
  
public class Test  
{  
    public static void main(String[] args)  
    {  
        //模拟一个3D的服务号  
        ObjectFor3D subjectFor3d = new ObjectFor3D();  
        //客户1  
        Observer observer1 = new Observer1(subjectFor3d);  
        Observer observer2 = new Observer2(subjectFor3d);  
  
        subjectFor3d.setMsg("20140420的3D号码是:127" );  
        subjectFor3d.setMsg("20140421的3D号码是:333" );  
          
    }  
}  

输出结果:

observer1 得到 3D 号码  -->20140420的3D号码是:127, 我要记下来。  
observer2 得到 3D 号码 -->20140420的3D号码是:127我要告诉舍友们。  
observer1 得到 3D 号码  -->20140421的3D号码是:333, 我要记下来。  
observer2 得到 3D 号码 -->20140421的3D号码是:333我要告诉舍友们。  

对于JDK或者Andorid中都有很多地方实现了观察者模式,比如XXXView.addXXXListenter , 当然了 XXXView.setOnXXXListener不一定是观察者模式,因为观察者模式是一种一对多的关系,对于setXXXListener11的关系,应该叫回调。

恭喜你学会了观察者模式,上面的观察者模式使我们从无到有的写出,当然了java中已经帮我们实现了观察者模式,借助于java.util.Observablejava.util.Observer

下面我们使用Java内置的类实现观察者模式 

首先是一个3D彩票服务号主题:

package com.zhy.pattern.observer.java;  
  
import java.util.Observable;  
  
public class SubjectFor3d extends Observable  
{  
    private String msg ;   
      
      
    public String getMsg()  
    {  
        return msg;  
    }  
  
  
    /** 
     * 主题更新消息 
     *  
     * @param msg 
     */  
    public void setMsg(String msg)  
    {  
        this.msg = msg  ;  
        setChanged();  
        notifyObservers();  
    }  
}  

但是为什么要加入这样一个开关呢?可能原因大致有三点

1.筛选有效通知,只有有效通知可以调用setChanged。比如,我的微信朋友圈一条状态,好友A点赞,后续该状态的点赞和评论并不是每条都通知A,只有A的好友触发的操作才会通知A

2.便于撤销通知操作,在主题中,我们可以设置很多次setChanged,但是在最后由于某种原因需要取消通知,我们可以使用clearChanged轻松解决问题。

3.主动权控制,由于setChangedprotected,notifyObservers方法为public,这就导致存在外部随意调用notifyObservers的可能,但是外部无法调用setChanged,因此真正的控制权应该在主题这里。

下面是一个双色球的服务号主题:

package com.zhy.pattern.observer.java;  
  
import java.util.Observable;  
  
public class SubjectForSSQ extends Observable  
{  
    private String msg ;   
      
      
    public String getMsg()  
    {  
        return msg;  
    }  
  
  
    /** 
     * 主题更新消息 
     *  
     * @param msg 
     */  
    public void setMsg(String msg)  
    {  
        this.msg = msg  ;  
        setChanged();  
        notifyObservers();  
    }  
}  

最后是我们的使用者:

package com.zhy.pattern.observer.java;  
  
import java.util.Observable;  
import java.util.Observer;  
  
public class Observer1 implements Observer  
{  
  
    public void registerSubject(Observable observable)  
    {  
        observable.addObserver(this);  
    }  
  
    @Override  
    public void update(Observable o, Object arg)  
    {  
        if (o instanceof SubjectFor3d)  
        {  
            SubjectFor3d subjectFor3d = (SubjectFor3d) o;  
            System.out.println("subjectFor3d's msg -- >" + subjectFor3d.getMsg());  
        }  
  
        if (o instanceof SubjectForSSQ)  
        {  
            SubjectForSSQ subjectForSSQ = (SubjectForSSQ) o;  
            System.out.println("subjectForSSQ's msg -- >" + subjectForSSQ.getMsg());  
        }  
    }  
}  

看一个测试代码:

package com.zhy.pattern.observer.java;  
  
public class Test  
{  
    public static void main(String[] args)  
    {  
        SubjectFor3d subjectFor3d = new SubjectFor3d() ;  
        SubjectForSSQ subjectForSSQ = new SubjectForSSQ() ;  
          
        Observer1 observer1 = new Observer1();  
        observer1.registerSubject(subjectFor3d);  
        observer1.registerSubject(subjectForSSQ);  
          
          
        subjectFor3d.setMsg("hello 3d'nums : 110 ");  
        subjectForSSQ.setMsg("ssq'nums : 12,13,31,5,4,3 15");  
          
    }  
}  

测试结果:

subjectFor3d's msg -- >hello 3d'nums : 110   
subjectForSSQ's msg -- >ssq'nums : 12,13,31,5,4,3 15  

可以看出,使用Java内置的类实现观察者模式,代码非常简洁,对了addObserver,removeObserver,notifyObservers都已经为我们实现了,所有可以看出Observable(主题)是一个类,而不是一个接口,基本上书上都对于Java的如此设计抱有反面的态度,觉得Java内置的观察者模式,违法了面向接口编程这个原则,但是如果转念想一想,的确你拿一个主题在这写观察者模式(我们自己的实现),接口的思想很好,但是如果现在继续添加很多个主题,每个主题的ddObserver,removeObserver,notifyObservers代码基本都是相同的吧,接口是无法实现代码复用的,而且也没有办法使用组合的模式实现这三个方法的复用,所以我觉得这里把这三个方法在类中实现是合理的。

主动获取

观察者模式即所谓的推送方式,然而推送并非完美无缺。比如主题变化会推送大量的数据,而其中的一些观察者只需要某项数据,此时观察者就需要在具体实现中花费时间筛选数据。

这确实是个问题,想要解决也不难,需要主题为某些数据提供getter方法,观察者只需调用getter取数据处理即可。

static class MilkProvider extends Observable {
    public void milkProduced() {
      setChanged();//状态改变,必须调用
      notifyObservers();
    }

    public float getPrice() {
      return 2.5f;
    }
  }

  static class Consumer implements Observer {
    @Override
    public void update(Observable arg0, Object arg1) {
        MilkProvider provider = (MilkProvider)arg0;
        System.out.println("milk price =" + provider.getPrice());
    }
  }

不足与隐患

主要的问题表现在内存管理上,主要由以下两点

1. 主题持有观察者的引用,如果未正常处理从主题中删除观察者,会导致观察者无法被回收。

2. 如果观察者具体实现代码有问题,会导致主题和观察者对象形成循环引用,在某些采用引用计数的垃圾回收器可能导致无法回收。

相关类介绍:

public class Observable extends Object

此类表示模型视图范例中的 observable 对象,或者说数据。可将其子类化,表示应用程序想要观察的对象。 

一个 observable 对象可以有一个或多个观察者。观察者可以是实现了 Observer 接口的任意对象。一个 observable 实例改变后,调用 Observable notifyObservers 方法的应用程序会通过调用观察者的 update 方法来通知观察者该实例发生了改变。 

未指定发送通知的顺序。Observable 类中所提供的默认实现将按照其注册的重要性顺序来通知 Observers,但是子类可能改变此顺序,从而使用非固定顺序在单独的线程上发送通知,或者也可能保证其子类遵从其所选择的顺序。 

注意,此通知机制与线程无关,并且与 Object 类的 wait notify 机制完全独立。 
新创建一个 observable 对象时,其观察者集是空的。当且仅当 equals 方法为两个观察者返回 true 时,才认为它们是相同的。 
--------------------------------------------------------------------------------
构造方法摘要 
Observable() 
          构造一个带有零个观察者的 Observable 
  方法摘要 
void addObserver(Observer o) 
          如果观察者与集合中已有的观察者不同,则向对象的观察者集中添加此观察者。 
protected  void clearChanged() 
          指示对象不再改变,或者它已对其所有的观察者通知了最近的改变,所以 hasChanged 方法将返回 false 
 int countObservers() 
          返回 Observable 对象的观察者数目。 
 void deleteObserver(Observer o) 
          从对象的观察者集合中删除某个观察者。 
 void deleteObservers() 
          清除观察者列表,使此对象不再有任何观察者。 
 boolean hasChanged() 
          测试对象是否改变。 
 void notifyObservers() 
          如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。 
 void notifyObservers(Object arg) 
          如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。 
protected  void setChanged() 

          标记此 Observable 对象为已改变的对象;现在 hasChanged 方法将返回 true 

 

public interface Observer
一个可在观察者要得到 observable 对象更改通知时可实现 Observer 接口的类。 
--------------------------------------------------------------------------------
方法摘要 
 void update(Observable o, Object arg) 
          只要改变了 observable 对象就调用此方法。 

工厂模式

 工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

工厂模式在《Java与模式》中分为三类:
       1)简单工厂模式(Simple Factory

2)工厂方法模式(Factory Method

3)抽象工厂模式(Abstract Factory

简单工厂模式

1.为什么要使用工厂模式

直接目的:避免在代码中出现大量的new关键字

根本目的:将对象的创建统一起来便于维护和整体把控

这一点可以理解,加入你在项目中new了某个对象100次,一年后由于业务逻辑变更,构造方法多了一个参数,你会怎么办?你应该会这么做:找到这100个对象new的地方,用新的构造方法来创建对象,你重复劳动了100次,假如采用工厂模式,你只用改一次:把创建工厂给改一下就好了。这就是工厂模式最简单最直接的好处。

2.工厂模式的示例

下面是最常见的一个示范,其实它的原理就是面向对象中的多态+接口编程,虽然返回的都是Car类型,但是drive的时候会调用真正的实例中的对应方法。按照抽象类和接口的意义归属,Car应该被定义成抽象类,因为BenzBmwCar的关系是继承关系,而接口表示的一组行为。但是,为什么这里还要定义成接口,是因为Java.NET不支持多继承,如果继承了Car,就无法继承其他类,有时候业务需要必须继承其他类,这个时候代码就不能用了。当然,C++中支持多继承,因此可以在C++中使用抽象类,另外C++没接口的概念,但你可以模拟接口。这里只介绍简单工厂模式,至于剩下两种工厂模式,我觉得更没有使用价值。

interface Car {  
    void drive();  
}  
  
class Benz implements Car {  
  
    @Override  
    public void drive() {  
        System.out.println("drive Benz");  
    }  
  
}  
  
class Bmw implements Car {  
  
    @Override  
    public void drive() {  
        System.out.println("drive Bmw");  
    }  
  
}  
  
class CarFactory {  
    public static Car creator(String carType) {  
        if (carType.equals("Benz")) {  
            return new Benz();  
        } else if (carType.equals("Bmw")) {  
            return new Bmw();  
        } else {  
          throw new UnsupportedOperationException("car with type" + carType 
                    + " is not supported.");  
        }  
    }  
}  
  
public class A {  
    public static void main(String args[]) {  
        Car benz = CarFactory.creator("Benz");  
        benz.drive();  
        Car bmw = CarFactory.creator("Bmw");  
        bmw.drive();  
    }  
}  

Android 开发中常用的设计模式:

1模板模式,Activity:
      每次新建一个Actiivty时都会覆盖onCreateonStart等方法,这些方法在   父类中就相当于一个模板
2、观察者模式
     Listener都相当于一个观察者,对一些事件的响应都进行观察,当发现有响应就进行做相应的处理
3、单例模式

  1. Application 单例模式  

  目的:

   希望对象只创建一个实例,并且提供一个全局的访问点。

 结构是简单的,但是却存在以下情况;

     1.每次从getInstance()都能返回一个且唯一的一个对象。

     2.资源共享情况下,getInstance()必须适应多线程并发访问。

     3.提高访问性能。

     4.懒加载(Lazy Load),在需要的时候才被构造。


4适配器模式
   适配器模式是一种重要的设计模式,在 android 中得到了广泛的应用。适配器类似于现实世界里面的插头,通过适配器,我们可以将分属于不同类的两种不同类型的数据整合起来,而不必去根据某一需要增加或者修改类里面的方法。

 android 中常见的适配器类有: BaseAdapter  SimpleAdapter  ,首先我们看看 android 应用层是如何使用适配器的:

综合 listview  gallery ,发现它们有着类似的实现过程,在 setAdapter 里面获取 适配的 item 的个数,然后通知各自的控件构造这些 item ,构造的时候会通过适配器来获取需要适配的 view

5、工厂模式

android中的应用:创建位图
例如:
Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_search)

这条语句相信我们经常用到,BitmapFactory顾名思义,就是位图工厂,专门用来将制定的图片转化为指定的位图Bitmap。因为图片来源的不同可能导致图片大小,格式类型等的多种多样,这样就导致了生成目标对象的复杂度,因此通过工厂统一的加工成同样大小,类型的标准件“,大大简化了代码的复杂度与工作量。如activity 的基类父类等,抽象父类来生成对应的实际子类。

6、代理模式

Android系统中利用AIDL定义一种远程服务时就需要用到代理

模式。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值