设计模式分为三种类型,共23种:
创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。
单例模式
每个类只能创建一个实例对象
Java 单例模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。 使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。
好处:
第一、控制资源的使用,通过线程同步来控制资源的并发访问;
第二、控制实例产生的数量,达到节约资源的目的。
第三、作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。
package javatest;
//设计模式之单例模式
public class Singleton {
String name = null;
private Singleton(){
};
private static volatile Singleton instance = null;
//懒汉式写法(线程安全, 不带synchronized就是线程不安全
public static Singleton getInstance(){
if (instance == null) {
synchronized (Singleton.class) {
//synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等,
//要不然在多线程情况下可能2个线程同时创建对象
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
/*
* 饿汉式写法, 类加载时直接将对象创建好, 用方法直接返回, 不实时创建
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
*/
/*
*静态内部类写法
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
*/
/*
* 枚举写法
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
*/
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public void getInfo(){
System.out.println("name is: " + name);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Singleton s1 = Singleton.getInstance();
s1.setName("kevin");
Singleton s2 = Singleton.getInstance();
s2.setName("shelley");
s1.getInfo();
s2.getInfo();
if (s1.equals(s2)) {
System.out.println("是一个实例");
}else {
System.out.println("不是一个实例");
}
}
}
观察者模式
观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象。这样一来,当被观察者状态发生改变时,需要通知相应的观察者,使这些观察者对象能够自动更新。例如:GUI中的事件处理机制采用的就是观察者模式。
流程:
- 创建被观察者(自己的属性, 被观察者注册删除通知方法, 被观察者列表)
- 创建多个观察者(自己的属性, 被通知后需要做的事情)
- 将观察者注册并加入被观察者的通知列表
- 被观察者属性变动时,触发通知方法通知所有观察者
package javatest;
import java.util.ArrayList;
//设计模式-观察者模式
interface Observable{
public void registerObserve(Observer observer);
public void removeObserve(Observer observer);
public void notifyObserver();
}
class Beenobserve implements Observable{
private ArrayList<Observer> olist = new ArrayList<>();
private float price;
public Beenobserve(float price){
this.price = price;
}
public float getPrice(){
return price;
}
public void setPrice(float price){
this.price = price;
notifyObserver();
}
@Override
public void registerObserve(Observer observer){
olist.add(observer);
}
@Override
public void removeObserve(Observer observer){
olist.remove(observer);
}
@Override
public void notifyObserver(){
for(Observer observer:olist){
observer.update(price);
}
}
}
interface iObserver {
public void update(float price);
}
public class Observer implements iObserver{
private String name;
public Observer(String name){
this.name = name;
}
@Override
public void update(float price){
System.out.println(name + "关注的杯子的价格已更新为: " + price);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Beenobserve beb = new Beenobserve(3000);
Observer o1 = new Observer("kevin");
Observer o2 = new Observer("shelley");
beb.registerObserve(o1);
beb.registerObserve(o2);
System.out.println("第一次修改价格");
beb.setPrice(3200);
System.out.println("第二次修改价格");
beb.setPrice(2800);
beb.removeObserve(o2);
System.out.println("第三次修改价格");
beb.setPrice(2000);
}
}
工厂模式
优点:
将创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建,明确了职责。
把初始化实例时的工作放到工厂里进行,使代码更容易维护。 更符合面向对象的原则,面向接口编程,而不是面向实现编程。
缺点:
由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
要新增产品类的时候,就要修改工厂类的代码,违反了开放封闭原则(对扩展的开放,对修改的关闭)。(可以用反射机制解决)
简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
package javatest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//设计模式之-工厂模式
interface Sender{
public void Send();
}
class Mailsender implements Sender{
@Override
public void Send(){
System.out.println("this is mail sender");
}
}
class Smssender implements Sender{
@Override
public void Send(){
System.out.println("this is sms sender");
}
}
class Wechatsender implements Sender{
@Override
public void Send(){
System.out.println("this is wechat sender");
}
}
public class Factory {
//普通工厂模式
public Sender produce(String type){
if("mail".equals(type)){
return new Mailsender();
}else if("sms".equals(type)){
return new Smssender();
}else if("wechat".equals(type)){
return new Wechatsender();
}else{
System.out.println("类型不正确");
return null;
}
}
//多工厂方法模式
public Sender produceMail(){
return new Mailsender();
}
public Sender produceSms(){
return new Smssender();
}
//通过反射机制, 定义通用工厂, 接收参数决定生产的产品
public void produceOjbect(String s) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{
String className = s; //需要运行时加载的类
Class clz3 = Class.forName(className); //使用forName静态方法加载类
Object obj = clz3.newInstance();
Method nameMethod1 = clz3.getMethod("Send"); //第二个值是该方法型参的类型,有多个型参用逗号隔开;方法没有型参,忽略第二个参数
//调用此方法
nameMethod1.invoke(obj);
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//普通工厂模式
Factory f = new Factory();
Sender s = f.produce("sms");
s.Send();
//多工厂模式
Factory f2 = new Factory();
Sender s2 = f2.produceMail();
s2.Send();
//反射模式通用工厂
Factory f3 = new Factory();
f3.produceOjbect("javatest.Wechatsender");
}
}
代理模式
基本概念:为其他对象提供一种代理以控制对这个对象的访问。也可以说,在出发点到目的地之间有一道中间层,意为代理。
为什么要使用
授权机制不同级别的用户对同一对象拥有不同的访问权利,如在论坛系统中,就使用Proxy进行授权机制控制,访问论坛有两种人:注册用户和游客(未注册用户),论坛就通过类似ForumProxy这样的代理来控制这两种用户对论坛的访问权限。
某个客户端不能直接操作到某个对象,但又必须和那个对象有所互动。
举例两个具体情况:
如果那个对象是一个是很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,打开文档必须很迅速,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片。
如果那个对象在Internet的某个远端服务器上,直接操作这个对象因为网络速度原因可能比较慢,那我们可以先用Proxy来代替那个对象。
总之原则是,对于开销很大的对象,只有在使用它时才创建,这个原则可以为我们节省很多宝贵的Java内存。所以,有些人认为Java耗费资源内存,我以为这和程序编制思路也有一定的关系。
如何使用
以论坛系统为例,访问论坛系统的用户有多种类型:注册普通用户、论坛管理者、系统管理者、游客。注册普通用户才能发言,论坛管理者可以管理他被授权的论坛,系统管理者可以管理所有事务等,这些权限划分和管理是使用Proxy完成的。
在Forum中陈列了有关论坛操作的主要行为,如论坛名称,论坛描述的获取和修改,帖子发表删除编辑等,在ForumPermissions中定义了各种级别权限的用户:
public class ForumPermissions implements Cacheable {
/**
* Permission to read object.
*/
public static final int READ = 0;
/**
* Permission to administer the entire sytem.
*/
public static final int SYSTEM_ADMIN = 1;
/**
* Permission to administer a particular forum.
*/
public static final int FORUM_ADMIN = 2;
/**
* Permission to administer a particular user.
*/
public static final int USER_ADMIN = 3;
public boolean isSystemOrForumAdmin() {
return (values[FORUM_ADMIN] || values[SYSTEM_ADMIN]);
}
//相关操作代码
}
因此,Forum中各种操作权限是和ForumPermissions定义的用户级别有关系的,作为接口Forum的实现:ForumProxy正是将这种对应关系联系起来。比如,修改Forum的名称,只有论坛管理者或系统管理者可以修改,代码如下:
public class ForumProxy implements Forum {
private ForumPermissions permissions;
private Forum forum;
this.authorization = authorization;
public ForumProxy(Forum forum, Authorization authorization,ForumPermissions permissions){
this.forum = forum;
this.authorization = authorization;
this.permissions = permissions;
}
.....
public void setName(String name) throws UnauthorizedException,
ForumAlreadyExistsException{
//只有是系统或论坛管理者才可以修改名称
if (permissions.isSystemOrForumAdmin()) {
forum.setName(name); //代理
}
else {
throw new UnauthorizedException();
}
}
...
}
而DbForum才是接口Forum的真正实现,以修改论坛名称为例:
public class DbForum implements Forum, Cacheable {
...
public void setName(String name) throws ForumAlreadyExistsException {
....
this.name = name;
//这里真正将新名称保存到数据库中
saveToDb();
....
}
...
}
凡是涉及到对论坛名称修改这一事件,其他程序都首先得和ForumProxy打交道,由ForumProxy决定是否有权限做某一样事情,ForumProxy是个名副其实的"网关",“安全代理系统”。
装饰者模式
Decorator模式有以下的优缺点:
比静态继承更灵活与对象的静态继承相比,Decorator模式提供了更加灵活的向对象添加职责的方式,可以使用添加和分离的方法,用装饰在运行时刻增加和删除职责。使用继承机制增加职责需要创建一个新的子类,如果需要为原来所有的子类都添加功能的话,每个子类都需要重写,增加系统的复杂度,此外可以为一个特定的Component类提供多个Decorator,这种混合匹配是适用继承很难做到的。
避免在层次结构高层的类有太多的特征,Decorator模式提供了一种“即用即付”的方法来添加职责,他并不试图在一个复杂的可订制的类中支持所有可预见的特征,相反可以定义一个简单的类,并且用Decorator类给他逐渐的添加功能,从简单的部件组合出复杂的功能。
Decorator 与它的Component不一样,Decorator是一个透明的包装,如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此使用装饰时不应该以来对象标识。
产生许多小对象,采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同。
流程:
- 创建接口类
- 创建具体需要被装饰的实体类, 实现接口
- 创建装饰类, 实现同一个接口
- 分别创建多个具体的装饰类, 继承于装饰类
package javatest;
//设计模式-装饰者模式
interface Daban{
public void chuanyi();
}
class Man implements Daban{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Man(String name){
this.name = name;
}
@Override
public void chuanyi(){
System.out.println("穿衣打扮后的" + name);
}
}
class DecoratorClient implements Daban{
private Daban mDaban;
public void decoratorObj(Daban daban){
mDaban = daban;
}
@Override
public void chuanyi(){
if (mDaban != null){
mDaban.chuanyi();
}
}
}
class Jeans extends DecoratorClient {
@Override
public void chuanyi(){
System.out.println("穿牛仔裤");
super.chuanyi();
}
}
class Shoes extends DecoratorClient {
@Override
public void chuanyi(){
System.out.println("穿鞋子");
super.chuanyi();
}
}
class Hat extends DecoratorClient {
@Override
public void chuanyi(){
System.out.println("戴帽子");
super.chuanyi();
}
}
public class Decorator {
public static void main(String[] args) {
// TODO Auto-generated method stub
Man p = new Man("Kevin");
Jeans j = new Jeans();
Shoes s = new Shoes();
Hat h = new Hat();
j.decoratorObj(p);
s.decoratorObj(j);
h.decoratorObj(s);
h.chuanyi();
}
}
适配器模式
将两种完全不同的事物联系到一起,就像现实生活中的变压器。假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。
根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,
在对象适配器模式中,适配器与适配者之间是关联关系;
在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
角色
Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
优点
更好的复用性:系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
更好的扩展性:在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
缺点
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
package javatest;
//设计模式-适配器模式
//类适配器
//已存在的将被适配的类(希望使用的方法)
class Adaptee {
public void adapteeRequest() {
System.out.println("被适配者的方法");
}
}
//目标接口(现有的类只能实现这个接口)
interface Target {
void request();
}
//现有的类
class Real implements Target{
@Override
public void request(){
System.out.println("自己的方法");
}
}
//定义一个适配器,同时实现Target接口和继承Adaptee类,然后在实现的 request() 方法中调用父类的 adapteeRequest()
class MakeAdapter extends Adaptee implements Target{
@Override
public void request() {
//...一些操作...
super.adapteeRequest();
//...一些操作...
}
}
//对象适配器(注意这里的 MakeAdapter 是将 Adaptee 作为一个成员属性,而不是继承它)
/*
class MakeAdapter implements Target{
// 适配者是对象适配器的一个属性
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
//...
adaptee.adapteeRequest();
//...
}
}
*/
public class Adapter {
public static void main(String[] args) {
// TODO Auto-generated method stub
Target r = new Real();
r.request();
//目标接口的对象也可以调用适配者的方法了
Target makeA = new MakeAdapter();
makeA.request();
}
}