设计模式的概念:
1. 设计模式是解决方案
2. 设计模式是特定问题的解决方案
3. 设计模式是重复出现的、特定问题的解决放案
4. 设计模式是用于解决特定环境下、重复出现的、特定问题的解决方案
5. 设计模式是经过验证的,用于解决在特定环境下、重复出现的、特定问题的解决方案
(1) 简单工厂模式
Java的开发是面向接口的编程
接口的思想就是“封装隔离”
简单工厂定义:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以使接口、抽象类,也可以使具体的类。
定义接口
packagecom.laizhibing.jiekou;
public interface Api {
/*
* 示意,具体功能方法的定义
* */
public void operation(String s);
}
定义接口实现类 ImplA
packagecom.laizhibing.shixian;
importcom.laizhibing.jiekou.Api;
public class ImplA implements Api{
@Override
public void operation(String s) {
// TODO Auto-generated method stub
//实现功能的代码
System.out.println("ImplA s=="+s);
}
}
定义接口实现类 ImplB
packagecom.laizhibing.shixian;
importcom.laizhibing.jiekou.Api;
public class ImplB implements Api {
@Override
public void operation(String s) {
// TODO Auto-generated method stub
//实现功能的代码,示意一下
System.out.println("ImplB s=="+s);
}
}
定义工厂类
packagecom.laizhibing.shixian;
importcom.laizhibing.jiekou.Api;
/*
* 工厂类,用来创建Api对象
* */
public class Factory {
/*
* 具体创建Api对象的方法
* */
public static Api createApi(int condition){
Api api = null;
if(condition == 1){
api = new ImplA();
}
else if(condition == 2){
api = new ImplB();
}
return api;
}
}
定义客户端类
packagecom.laizhibing.client;
import com.laizhibing.jiekou.Api;
importcom.laizhibing.shixian.Factory;
/*
* 客户端类,使用Api接口
*
* */
public class Client {
public static void main(String[] args) {
//通过简单工厂来获取接口对象
Api api = Factory.createApi(1);
api.operation("正在使用简单工厂");
}
}
认识简单工厂
1. 简单工厂的功能:在Java里面,通常情况下是用来创造接口的,但是也可以创造抽象类
2. 静态工厂:简单工厂的方法通常是静态的,所以也被称为静态工厂。
3. 万能工厂:一个简单工厂理论上可以构造任何东西,所以称为“万能工厂”
4. 简单工厂命名的建议
(1) 类名称建议为“模块名称+Factroy”。比如:用户模块的工厂就称为UserFactroy
(2) 方法名称通常为“get+接口名称 或者是“create+接口名称.比如,有一个接口名称为UserEbi,那么方法名称通常为getUserEbi()
可配置的简单工厂
上面每次新增加一个实现类都来修改工厂类的实现,肯定不是一个好的实现方式,解决的办法是使用配置文件,当有了新的实现类后,只要在配置文件里面配置上新的实现类即可。在简单工厂的方法里面可以使用反射。
(1) 配置文件使用最简单的properties文件,实际开发中多是xml配置,定义一个名称为“FactoryTest.properties”的配置文件,放置到Factroy同一个包下面
FactoryTest.properties
ImplClass=com.laizhibing.shixian.Impl
Impl实现接口类
packagecom.laizhibing.shixian;
importcom.laizhibing.jiekou.Api;
public class Impl implements Api {
@Override
public void operation(String s) {
// TODO Auto-generated method stub
System.out.println(s);
}
}
Factory实现取得配置类的实现接口类
packagecom.laizhibing.shixian;
importjava.io.InputStream;
importjava.util.Properties;
importcom.laizhibing.jiekou.Api;
/*
* 工厂类,用来创建Api对象
* */
public class Factory {
public static Api createApi(){
//直接读取配置文件来获取需要创建实例的类
//至于如何读取Properties,还有如何反射在这里就不解释了
Propertiesp = new Properties();
InputStream in = null;
try {
in = Factory.class.getResourceAsStream("FactoryTest.properties");
p.load(in);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
try {
in.close();
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
}
//用反射区区创建,那些例外处理等完善的工作这里就不做了
Api api = null;
try {
api = (Api)Class.forName(p.getProperty("ImplClass")).newInstance();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return api;
}
}
Client实现类
package com.laizhibing.client;
importcom.laizhibing.jiekou.Api;
importcom.laizhibing.shixian.Factory;
/*
* 客户端类,使用Api接口
*
* */
public class Client {
public static void main(String[] args) {
//通过简单工厂来获取接口对象
Api api = Factory.createApi();
api.operation("哈哈,不要紧张,只是个测试而已!");
}
}
这样就避免了以后增加实现类时,去修改Factory工厂类
简单工厂的优缺点
优点
(1) 帮助封装:简单工厂虽然简单,但是非常友好地帮助我们实现了组件的封装,然后让外部能真正面向接口编程
(2) 解耦:通过简单工厂,实现了客户端和具体实现类的解耦。
缺点
(1) 可能增加客户端的复杂度:如果通过客户端的参数来选择具体的实现类,那么就必须让让客户端能理解各个参数所代表的具体功能和含义,这样会增加客户端使用的难度
(2) 不方便扩展子工厂
思考简单工厂
(1) 简单工厂的本质:选择实现
(2) 何时选用简单工厂
相关模式
简单模式和抽象工厂模式
简单工厂是用来选择实现的,可以选择任意接口的实现
外观模式
外观模式定义:为子系统中的一组接口提供一个一致的界面,Façade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
这里的界面指的的从一个组件外部来看这个组件,能够看到什么,这就是这个组件的界面
外观模式的结构和说明
1. Façade:定义子系统的多个模块对外的高层接口,通常需要调用内部多个模块,从而把请求代理给适当的子系统对象。
2. 模块:接受Façade对象的委派,真正实现功能,各个模块之间可能有交互。
实例
接口
packagecom.jiekou;
public interface AModuleApi {
public void testA();
}
B接口
packagecom.jiekou;
public interface BModuleApi {
public void testB();
}
C接口
packagecom.jiekou;
public interface CModuleApi {
public void testC();
}
接口实现类
packagecom.shixian;
importcom.jiekou.AModuleApi;
public class AModuleImpl implements AModuleApi {
@Override
public void testA() {
// TODO Auto-generated method stub
System.out.println("现在在A模块里面操作testA方法");
}
}
packagecom.shixian;
importcom.jiekou.BModuleApi;
public class BModuleImpl implements BModuleApi {
@Override
public void testB() {
// TODO Auto-generated method stub
System.out.println("现在在B模块里面操作testB方法");
}
}
packagecom.shixian;
importcom.jiekou.CModuleApi;
public class CModuleImpl implements CModuleApi {
@Override
public void testC() {
// TODO Auto-generated method stub
System.out.println("现在在C模块里面操作testC方法");
}
}
Façade类
packagecom.shixian;
/*
* 外观对象
* */
public class Facade {
public void test(){
//在内部实现的时候,可能会调用到内部的多个模块
AModuleImpl a = new AModuleImpl();
a.testA();
BModuleImpl b = new BModuleImpl();
b.testB();
CModuleImpl c = new CModuleImpl();
c.testC();
}
}
Client类
packagecom.client;
import com.shixian.Business;
import com.shixian.DAO;
importcom.shixian.Facade;
import com.shixian.Presentation;
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//现在没有配置文件,直接使用默认的配置,通常情况下,三层都应该生成
// new Presentation().generate();
// new Business().generate();
// new DAO().generate();
new Facade().test();
}
}
认识外观模式
(1) 外观模式的目的:不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统。
(2) 使用外观和不使用外观相比有何变化
(3) 有外观,但是可以不使用
(4) 外观提供了缺省的功能实现
外观模式的实现
(1) Façade实现:对于一个系统而言,外观类不需要很多,通常可以实现成为一个单例,也可以直接把外观中的方法实现成为静态的方法,这样就可以不需要创建外观对象的实例而直接调用。
(2) Façade可以实现成为interface:虽然Façade通常直接实现成为类,但是也可以把Façade实现成为真正的interface,只是这样会增加系统的复杂程度,因为这样会需要一个Façade的实现,还需要一个取Façade接口对象的工厂,
(3) Façade实现成为interface的附带好处:如果把Façade实现成为接口,还附带一个功能,就是能够有选择性地暴露接口的方法,尽量减少模块对子系统外提供的接口方法。
packagecom.jiekou;
public interface AModuleApi {
public void a1();
public void a2();
public void a3();
}
packagecom.jiekou;
public interface BModuleApi {
public void b1();
public void b2();
public void b3();
}
packagecom.jiekou;
public interface CModuleApi {
public void c1();
public void c2();
public void c3();
}
package com.jiekou
public interfaceFacadeApi{
public void a1(); //这些是A,B,C模块对子系统外的接口,这样外部就不需要知道
public void a2(); //A,B,C,模块的存在只需要知道Façade接口就行。
public void a3();
public void test(); //这是对外提供的组合方法
}
换句话说:一个模块的接口中定义的方法可以分成两部分,一部分是给予子系统外部使用的,一部分是子系统内部的模块间相互调用时使用的。有了Façade接口,那么用于子系统内部的接口功能就不用暴露给子系统的外部了。
外观模式的本质:封装交互,简化调用。
适配器模式
适配器模式定义:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
认识适配器
(1) 模式的功能:主要是转换匹配,目的是复用已有的功能,而不是实现新的接口。
(2) Adapter与Target的关系:适配器模式中的被适配接口Adaptee和适配成为接口的Target是没有关联的。也就是说Adaptee和Target中的方法可以相同也可以不同。
(3) 对象组合:适配器的实现方式其实是对象组合的方式,通过给适配器对象组合被适配的对象。
适配器模式的实现
(1) 适配器的常见实现:适配器通常是一个类。
(2) 智能适配器:实现一些Adaptee没有实现,但是在Target中定义的功能
(3) 适配多个Adaptee
(4) 适配器Adapter实现的复杂程度:取决于Target和Adaptee的相似程度
(5) 缺省匹配
双向适配器:顾名思义就是Adapter类同时实现Target和Adaptee接口
对象适配器和类适配器
(1) 对象适配器的实现依赖于对象组合。
(2) 类适配器的实现采用多重继承对一个接口与另一个接口匹配,由于Java不支持多继承,所以继承一个类和实现一个接口解决。
适配器的优缺点
优点:更好 的复用性,更好的扩展性
缺点:过多的使用适配器,会让系统非常零乱,不容易整体进行把握。
单例模式
单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
packagecom.client;
importcom.danli.AppConfig;
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建读取应用配置的对象
AppConfig config = new AppConfig();
String paramA = config.getParameterA();
String paramB = config.getParameterB();
System.out.println("paramA="+paramA+",paramB="+paramB);
}
}
仔细分析上面的问题,现在一个类能够被创建多个实例,问题的根源在于类 的构造方式是公开的,也就是可以让外部来通过构造方法创建多个实例
在Java中,单例模式的实现又分为2种,一种称为懒汉式,一种称为饿汉式,其实就是在具体创建对象实例的处理上,有不同的实现方式。
懒汉式
packagecom.danlimoshi;
/*
* 懒汉式单例实现的示例
* */
public class SingletonLanHan {
/*
* 定义一个变量来存储创建好的类实例
*
* */
private static SingletonLanHan uniqueInstance = null;
/*
* 私有构造方法
* */
private SingletonLanHan() {
// TODO Auto-generated constructor stub
}
/*
* 定义一个方法来为客户端提供类实例
* */
public static synchronized SingletonLanHan getInstance(){
//判断存储实例的变量是否有值
if(uniqueInstance == null)
uniqueInstance = new SingletonLanHan();
//如果有值,就直接使用
return uniqueInstance;
}
/*
* 示例方法,单例可以有自己的操作
* */
public void singleLonOperation(){
//功能处理
}
/*
* 可以有自己的属性
* */
private String singletonData;
}
饿汉式
packagecom.danlimoshi;
/*
* 懒汉式单例实现的示例
* */
public class SingletonEHan {
/*
* 定义一个变量来存储创建好的类实例
*
* */
private static SingletonEHan uniqueInstance = new SingletonEHan();
/*
* 私有构造方法
* */
private SingletonEHan() {
// TODO Auto-generated constructor stub
}
/*
* 定义一个方法来为客户端提供类实例
* */
public static synchronized SingletonEHan getInstance(){
return uniqueInstance;
}
/*
* 示例方法,单例可以有自己的操作
* */
public void singleLonOperation(){
//功能处理
}
/*
* 可以有自己的属性
* */
private String singletonData;
}
认识单例模式
(1)单例模式是用来保证这个类在运行期间只会被创建一个类实例。另外,单例模式还提供了全局唯一访问这个类实例的访问点,就是getInstance()方法,不管采用懒汉式还是饿汉式,这个全局访问点是一样的。
(2)单例模式的范围:目前Java里面实现的单例是一个虚拟机的范围,因为装载类的功能是虚拟机的,根据虚拟机中的ClassLoader()而定
(3)单例模式的命名:一般建议单例模式的命名为getInstance();
懒汉式和饿汉式实现
懒汉式步骤
(1) 私有化构造方法
(2) 提供获取实例的方法
(3) 把获取实例的方法变成静态的
(4) 定义存储实例的属性
(5) 把这个属性页定义成静态的
(6) 实现控制实例的创建
(7) 完整的实现
延迟加载的思想
单例模式的懒汉式实现方式体现了延迟加载的思想
延迟加载的定义:就是一开始不要加载资源或数据,一直等,等到马上就是实验这个资源或者数据了,躲不过去了才加载。
缓存的思想
单例的懒汉式实现还体现了缓存的思想,一个简单的解决办法是:把这些数据缓存到内存里面,每次操作的时候,先到内存里面找,看有没有这些数据,如果有,就直接使用,如果没有就获取它,并设置到缓存中去,下次访问就可以在内存中获取了,从而节省大量的时间。
缓存是一种典型的空间换时间的方案。
Java中缓存的实现
在Java中最常见的缓存的方式就是使用Map
packagecom.danlimoshi;
import java.util.HashMap;
importjava.util.Map;
public class huancun {
/*
* 缓存数据的容器
* */
private Map<String,Object> map = new HashMap<String,Object>();
/*
* 从缓存中获取值
* */
public Object getValue(String key){
//先从缓存里面取值
Object obj = map.get(key);
//判断缓存里面是否有值
if(obj == null){
//如果没有
obj = key+",value";
map.put(key, obj);
}
return obj;
}
}
单例模式的优缺点
(1) 时间和空间:懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。
(2) 饿汉式是典型的空间换时间,当加载的时候就会创建类实例,不管你用不用,先创建出来。
线程安全
(1) o从线程安全性来讲:不加同步的懒汉式线程是不安全的。
(2) 饿汉式是线程安全的
(3) 实现懒汉式线程安全,只要在创建实例的时候加上synchronized。
既能实现延迟加载,有能实现线程安全,加个内部静态类
packagecom.danlimoshi;
public class Singleton {
/*
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,而且只有被调用时才会装载,从而实现了延迟加载
* */
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
/*
* 私有化构造方法
* */
private Singleton(){
//
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
单例模式的本质是:控制实例数目。
package com.danlimoshi;
importjava.util.HashMap;
importjava.util.Map;
/*
* 简单演示如何扩展单例模式,控制实例数目为3个
* */
public classOneExtend {
/*
* 定义一个缺省的key值的前缀
* */
private final static String DEFAULT_PREKEY ="Cache";
/*
* 缓存实例 的容器
* */
private static Map<String,OneExtend>map = new HashMap<String,OneExtend>();
/*
* 用来记录当前正在使用第几个实例,到了控制的最大数目,就返回从1开始
* */
private static int num = 1;
/*
* 定义控制的最大数目
* */
private final static int NUM_MAX = 3;
private OneExtend(){}
public static OneExtend getInstance(){
String key = DEFAULT_PREKEY + num;
OneExtend oneExtend = map.get(key);
if(oneExtend == null){
oneExtend = new OneExtend();
map.put(key,oneExtend);
}
num++;
if(num > NUM_MAX){
num = 1;
}
return oneExtend;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
OneExtend t1 = getInstance();
OneExtend t2 = getInstance();
OneExtend t3 = getInstance();
OneExtend t4 = getInstance();
OneExtend t5 = getInstance();
OneExtend t6 = getInstance();
System.out.println("t1=="+t1);
System.out.println("t2=="+t2);
System.out.println("t3=="+t3);
System.out.println("t4=="+t4);
System.out.println("t5=="+t5);
System.out.println("t6=="+t6);
}
}
何时选用单例模式
当需要控制一个类的实例只能是一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。。。