设计模式简单介绍与应用

设计模式的具体场景应用案例。

Posted by chengweii on September 12, 2017

适配器模式(Adapter)

3头电器插到2头插板上需要一个转接头(适配器)。

通过接口A访问接口B,发现不兼容,但是不能改变接口A,于是创建一个适配器类P,实现接口A,实现内容为调用接口B,通过访问适配器类P来达到通过接口A访问接口B的效果。

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作,一般有两种实现方式:类适配器、对象适配器。

类适配器:通过继承来实现适配器功能。适配器实现接口A,通过继承接口B访问B接口。 类适配器 对象适配器:通过组合来实现适配器功能。适配器实现接口A,通过组合接口B访问B接口。 对象适配器

应用场景

  • Java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap、TreeMap都是线程不安全的,如果有多个线程同时访问它们,且同时有多个线程修改他们的时候,将会出现如读脏数据等错误。Collections给出了解决方案,提供了synchronizedCollection方法来实现线程安全,该方法返回一个线程安全容器,可以理解为适配器。从源码中可以看到,SynchronizedCollection关联现有的collection对象,通过委托的方式调用现有对象的方法,只做了synchronized同步处理。java.util.Collections.synchronizedCollection(Collection c);
public class Collections {
  public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
    return new SynchronizedCollection<>(c);
  }

  static class SynchronizedCollection<E> implements Collection<E>, Serializable {
    private static final long serialVersionUID = 3053995032091335093L;

    final Collection<E> c;  // Backing Collection
    final Object mutex;     // Object on which to synchronize

    SynchronizedCollection(Collection<E> c) {
      this.c = Objects.requireNonNull(c);
      mutex = this;
    }

    SynchronizedCollection(Collection<E> c, Object mutex) {
      this.c = Objects.requireNonNull(c);
      this.mutex = Objects.requireNonNull(mutex);
    }

    public int size() {
      synchronized (mutex) {return c.size();}
    }
    public boolean isEmpty() {
      synchronized (mutex) {return c.isEmpty();}
    }
    public boolean contains(Object o) {
      synchronized (mutex) {return c.contains(o);}
    }
    public Object[] toArray() {
      synchronized (mutex) {return c.toArray();}
    }
    public <T> T[] toArray(T[] a) {
      synchronized (mutex) {return c.toArray(a);}
    }

    public Iterator<E> iterator() {
      return c.iterator(); // Must be manually synched by user!
    }

    public boolean add(E e) {
      synchronized (mutex) {return c.add(e);}
    }
    public boolean remove(Object o) {
      synchronized (mutex) {return c.remove(o);}
    }

    public boolean containsAll(Collection<?> coll) {
      synchronized (mutex) {return c.containsAll(coll);}
    }
  }
}
  • 适配不同支付平台(银联、POS、支付宝、微信、财付通)的SDK到通用支付接口(支付、应答、查询)

    支付操作是订单处理的关键流程,一般与订单处理是紧耦合关系,由于支付方式有多种渠道,为了避免支付方式变化对订单处理的影响,通常将支付操作流程(支付、应答、查询)抽象为接口,针对具体支付方式实现相应的适配器,然后以切换相应适配器的方式对接各个支付渠道

观察者模式(Observer)

消息的订阅与发布。

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

通常观察者模式是以推送更新的方式实现的,推送的好处就是及时高效的通知订阅者,当然推送模式也有一些问题:

  • 推送消息过于频繁导致给部分对消息敏感度要求不高的订阅方造成压力(比如天气预报系统中,关于温度信息,0.001度的变化部分订阅方并不想得到通知)
  • 部分订阅方仅需部分数据,推送消息却为全部内容,导致订阅方被迫接收部分垃圾数据(比如天气预报系统中,部分订阅方只想了解最新的温度信息,并不关心湿度的变化)

拉取模式可以在一定程度上弥补以上缺点,主题仅推送更新标志,是否更新数据由订阅者自行决定,若需要更新则通过拉取接口获取更新数据。

在许多实现中,观察器的 update() 方法可能与主体在同一线程中执行。如果观察器列表很长,则执行 Notify() 方法可能需要很长时间,所以针对此种情况一般会采用另起线程通知更新的异步推送方式,避免影响主线程的处理流程。

在 Observer 中使用的回调机制(当对象注册为以后调用时)会产生一个常见的错误,从而导致内存泄漏。假定观察器超出作用范围,但忘记取消对主体的订阅,那么主体仍然保留对观察器的引用。此引用防止垃圾收集在主体对象也被破坏之前重新分配与观察器关联的内存。如果观察器的生存期比主体的生存期短得多(通常是这种情况),则会导致严重的内存泄漏。

主题维护订阅者、通知订阅者是通过读写主题对象的订阅者列表完成的,一般订阅者列表是通过ArrayList实现,但在涉及多线程并发的场景中,为了保证订阅者列表读写的线程安全,请使用Vector(写多读少情况)或CopyOnWriteArrayList(写少读多情况)。

应用场景

  • Spring事件监听机制的实现
public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext, DisposableBean {
  private ApplicationEventMulticaster applicationEventMulticaster;
  protected void registerListeners() {
        // Register statically specified listeners first.
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        }
        // Do not initialize FactoryBeans here: We need to leave all regular beans
        // uninitialized to let post-processors apply to them!
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String lisName : listenerBeanNames) {
            getApplicationEventMulticaster().addApplicationListenerBean(lisName);
        }
    }
}
public abstract class AbstractApplicationContext{
	public void publishEvent(ApplicationEvent event) {  
	    Assert.notNull(event, "Event must not be null");  
	    if (logger.isTraceEnabled()) {  
	        logger.trace("Publishing event in " + getDisplayName() + ": " + event);  
	    }  
	    //事件广播委托给ApplicationEventMulticaster来进行  
	    getApplicationEventMulticaster().multicastEvent(event);  
	    if (this.parent != null) {  
	        this.parent.publishEvent(event);  
	    }  
	}
}
public abstract class AbstractApplicationEventMulticaster 
        implements ApplicationEventMulticaster,BeanFactoryAware{
	private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);
	private final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
			new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64);
	private BeanFactory beanFactory;
	public void addApplicationListener(ApplicationListener listener) {
		synchronized (this.defaultRetriever) {
			this.defaultRetriever.applicationListeners.add(listener);
			this.retrieverCache.clear();
		}
	}
	public void addApplicationListenerBean(String listenerBeanName) {
		synchronized (this.defaultRetriever) {
			this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
			this.retrieverCache.clear();
		}
	}
	public void multicastEvent(final ApplicationEvent event) {  
	    for (final ApplicationListener listener : getApplicationListeners(event)) {  
	        Executor executor = getTaskExecutor();  
	        if (executor != null) {  
	            executor.execute(new Runnable() {  
	                public void run() {  
	                    listener.onApplicationEvent(event);  
	                }  
	            });  
	        }  
	        else {  
	            listener.onApplicationEvent(event);  
	        }  
	    }  
	} 
	private class ListenerRetriever {
		public final Set<ApplicationListener> applicationListeners;
		public final Set<String> applicationListenerBeans;
		private final boolean preFiltered;
		public ListenerRetriever(boolean preFiltered) {
			this.applicationListeners = new LinkedHashSet<ApplicationListener>();
			this.applicationListenerBeans = new LinkedHashSet<String>();
			this.preFiltered = preFiltered;
		}
		public Collection<ApplicationListener> getApplicationListeners() {
			LinkedList<ApplicationListener> allListeners = new LinkedList<ApplicationListener>();
			for (ApplicationListener listener : this.applicationListeners) {
				allListeners.add(listener);
			}
			if (!this.applicationListenerBeans.isEmpty()) {
				BeanFactory beanFactory = getBeanFactory();
				for (String listenerBeanName : this.applicationListenerBeans) {
					ApplicationListener listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
					if (this.preFiltered || !allListeners.contains(listener)) {
						allListeners.add(listener);
					}
				}
			}
			OrderComparator.sort(allListeners);
			return allListeners;
		}
	}
}
  • MVC架构中model与view的同步
  • 订单确认后的返券、送积分、记录日志等流程的处理

    订单确认完成后会有一系列的后续操作,这些操作的处理依赖订单确认状态的更新,由于可能会增加新后续操作(新增发送订单已确认邮件)或者操作本身产生业务变更(送积分逻辑调整),为了适应后续操作的变更,将订单处理抽象为主题,后续一系列操作抽象为观察者,通过解耦订单处理与后续操作来适应后续操作的变化。(注:积分返券等业务比较简单,未独立为子系统时,可以如此实现,后期随着业务发展变得复杂之后,用户、促销等业务会各自独立为子系统,此时一般通过消息队列的方式来完成订阅更新)

模版方法模式(Template)

不要过分关注细节,抓紧大方向就好。

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

模版方法模式

模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,抽象类中的方法分为三种:

  • 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。
  • 模版方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
  • 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。 抽象类的任务是搭建逻辑的框架,通常由经验丰富的人员编写,因为抽象类的好坏直接决定了程序是否稳定性。

实现类用来实现细节。抽象类中的模版方法正是通过实现类扩展的方法来完成业务逻辑。只要实现类中的扩展方法通过了单元测试,在模版方法正确的前提下,整体功能一般不会出现大的错误。

应用场景

  • Servlet使用
public abstract class HttpServlet extends GenericServlet implements java.io.Serializable {
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String protocol = req.getProtocol();
		String msg = lStrings.getString("http.method_get_not_supported");
		if (protocol.endsWith("1.1")) {
			resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
		} else {
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
		}
	}

	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String method = req.getMethod();

		if (method.equals(METHOD_GET)) {
			// ...
		} else if (method.equals(METHOD_HEAD)) {
			long lastModified = getLastModified(req);
			maybeSetLastModified(resp, lastModified);
			doHead(req, resp);
		} else if (method.equals(METHOD_POST)) {
			doPost(req, resp);
		}
		// ...
	}
}
public class DispatcherServlet extends FrameworkServlet {
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}
	@Override
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}
}
  • Spring扩展JdbcTemplate
//一、模版方法
public abstract class JdbcTemplate {
	// template method
	public final Object execute(String sql) throws SQLException {
		Connection con = HsqldbUtil.getConnection();
		Statement stmt = null;
		try {
			stmt = con.createStatement();
			ResultSet rs = stmt.executeQuery(sql);
			Object result = doInStatement(rs);// abstract method
			return result;
		} catch (SQLException ex) {
			ex.printStackTrace();
			throw ex;
		} finally {
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if (!con.isClosed()) {
					try {
						con.close();
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	// implements in subclass
	protected abstract Object doInStatement(ResultSet rs);
}
public class JdbcTemplateUserImpl extends JdbcTemplate {  
    @Override  
    protected Object doInStatement(ResultSet rs) {  
        List<User> userList = new ArrayList<User>();  
        try {  
            User user = null;  
            while (rs.next()) {  
  
                user = new User();  
                user.setId(rs.getInt("id"));  
                user.setUserName(rs.getString("user_name"));  
                user.setBirth(rs.getDate("birth"));  
                user.setCreateDate(rs.getDate("create_date"));  
                userList.add(user);  
            }  
            return userList;  
        } catch (SQLException e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
    public static void main(String[] args){
        String sql = "select * from User";  
        JdbcTemplate jt = new JdbcTemplateUserImpl();  
        List<User> userList = (List<User>) jt.execute(sql); 
    }
} 
//二、回调函数,优点:仅实现需要的回调函数即可,不需要实现抽象类中所有的抽象函数
public interface StatementCallback {  
    Object doInStatement(Statement stmt) throws SQLException;  
} 
public class JdbcTemplate {
	// template method
	public final Object execute(StatementCallback action) throws SQLException {
		Connection con = HsqldbUtil.getConnection();
		Statement stmt = null;
		try {
			stmt = con.createStatement();
			Object result = action.doInStatement(stmt);// abstract method
			return result;
		} catch (SQLException ex) {
			ex.printStackTrace();
			throw ex;
		} finally {
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			try {
				if (!con.isClosed()) {
					try {
						con.close();
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}
public class Test{
    public Object query2(final String sql) throws Exception{  
        JdbcTemplate jt = new JdbcTemplate();  
        return jt.query(new StatementCallback() {  
            public Object doInStatement(Statement stmt) throws SQLException {  
                ResultSet rs = stmt.executeQuery(sql);  
                List<User> userList = new ArrayList<User>();  
                User user = null;  
                while (rs.next()) {  
                    user = new User();  
                    user.setId(rs.getInt("id"));  
                    user.setUserName(rs.getString("user_name"));  
                    user.setBirth(rs.getDate("birth"));  
                    user.setCreateDate(rs.getDate("create_date"));  
                    userList.add(user);  
                }  
                return userList;  
            }  
        });  
    }  
}
  • 订单运费计算

    订单运费针对不同承运方式(物流(大件便宜、速度较慢)、快递(小件便宜,速度较快))有各自的算法,算法的基本内容是一致的:由于自营、商户商品的运费算法是不同的(按地区收费、按重收费、满额收费、免邮),需要针对不同承运方式分别计算订单中自营、商户商品的运费,然后相加得出最终运费。采用模版方法模式,订单运费的基本算法定义为模版,不同承运方式的运费具体算法定义为子类。

命令模式(Command)

请求参数化。

将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

命令模式

在下面的情况下应当考虑使用命令模式: 1.使用命令模式作为”CallBack”在面向对象系统中的替代。”CallBack”讲的便是先将一个函数登记上,然后在以后调用此函数。 2.需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。 3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。 4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。

应用场景

  • Macro(宏)批量命令的执行
  • 多线程的Runnable
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
	public static void main(String[] args) {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			final int index = i;
			try {
				Thread.sleep(index * 1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			cachedThreadPool.execute(new Runnable() {
				public void run() {
					System.out.println(index);
				}
			});
		}
	}
}