设计模式类型 | 个数 | 内容 |
---|---|---|
创建型模式 | 5 | 工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 |
结构型模式 | 7 | 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式 |
行为型模式 | 11 | 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式 |
开闭原则 Open Close Principle
内容:开闭原则不允许修改原有代码,如果想要拓展程序,添加新功能,需要通过接口或者抽象类来实现。
好处:使程序拓展性更好,易于维护和升级。
里氏代换原则 Liskov Substitution Principle
内容:里氏代换原则是对面向对象中,子类与父类使用的诠释。任何父类出现的地方,其子类一定可以出现,同时,只有当子类可以完全替换掉父类,且程序功能不会受到影响时,父类才算真正被复用,子类可以在父类的基础上添加新的行为。里氏代换原则是对开闭原则的补充。想要实现开闭原则,需要用到抽象化,而里氏代换原则则是对实现抽象化的步骤规范。
依赖倒转原则 Dependence Inversion Principle
内容:开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则 Interface Segregation Principle
内容:提倡一个类使用多个隔离的接口,而尽量减少使用单个接口的次数。
优点:降低类与类之间的耦合性。(降低依赖,降低耦合)
迪米特原则/最少知道原则 Demeter Principle
内容:最少知道,一个实体应该尽量减少与其他实体之间发生相互作用,这样使得程序的功能模块相对独立,降低耦合。
合成复用原则 Composite Reuse Principk
内容:减少使用继承,尽量使用合成/聚合。
原因:继承在代码编写时便决定好了,不便于随时修改,同时子类对父类依赖太强,父类的变动会对子类有很大的影响。继承这种依赖关系限制了灵活性,并最终限制了复用性。
1. 普通工厂模式 : 建立一个工厂类,对实现了同一接口的类进行管理、实现。
创建接口(发送信息接口)
public interface Sender {
public void send(){
System.out.println("this is a interface");
}
}
创建两个实现类
public class EmailSender implements Sender{
public void send(){
System.out.println("this is Email");
}
}
public class MessageSender implements Sender{
public void send(){
System.out.println("this is Message");
}
}
创建工厂类
public SenderFactory{
public Sender createSender(String type){
if(type.equals("Email")){
return new EmailSender();
} else if(type.equals("Message")){
return new MessageSender();
} else {
System.out.println("输入有误!!");
return null;
}
}
}
main函数
public static void main(String[] args){
SenderFactory senderFactory = new SenderFactory();
Sender sender = senderFactory.createSender("Email");
sender.send();
}
输出结果:this is Email
2. 多个工厂模式 : 多个工厂模式是对单普通工厂模式的改进,普通工厂模式中如果字符串出错,则直接返回一个null值,多个工厂模式内则有多个对应的函数,能直接返回相应的对象。
对工厂类进行小幅修改
public class SenderFactory(){
public Sender createEmailSender(){
return new EmailSender();
}
public Sender createMessageSender(){
return new MessageSender();
}
}
//测试类
public static void main(String[] args){
SenderFactory senderFactory = new SenderFactory();
Sender sender = senderFactory.createEmailSender();
sender.sned();
}
//输出:this is Email
3.静态工厂方法模式 : 将上述工厂类方法设为静态,调用时不再需要创建对象。
public class SenderFactory(){
public static Sender createEmailSender(){
return new EmailSender();
}
public static Sender createMessageSender(){
return new MessageSender();
}
}
//测试类
public static void main(String[] args){
Sender sender = SenderFactory.createEmailSender();
sender.sned();
}
//输出:this is Email
工厂方法模式适用范围:
小总结:第一种模式如果字符串输入错误则失败,第三种模式相当于第二种,一般情况下选择第三种模式。
抽象工厂模式是对工厂方法模式的补充,工厂方法模式的缺点显而易见,如果想要新增功能,必须修改工厂类,这违背了开闭原则。抽象工厂模式则是建立多了工厂类,他们处理和功能对象一样,继承同一个接口,每次需要新增功能时只需要新加一个工厂类即可。下面是代码演示:
创建接口
public interface Sender {
public void send();
}
创建两个实现类
public class EmailSender implements Sender{
public void send(){
System.out.println("this is Email");
}
}
public class MessageSender implements Sender{
public void send(){
System.out.println("this is Message");
}
}
创建工厂类实现接口
public interface Factory {
public Sender create();
}
创建工厂类
public class EmailFactory implements Factory{
public static Sender create(){
return new EmailSender();
}
}
public class MessageFactory implements Factory{
public static Sender create(){
return new MessageSender();
}
}
测试类:
public static void main(String[] args){
Sender sender = EmailFactory.create();
sender.send();
}
//输出:this is Email
好处 : 其实好处显而易见,如果想增加功能,只需要添加一个工厂类,大幅度降低了耦合度。
单例类算是一个用处比较广泛的方法。在java应用中,单例类能够保证在一个Java虚拟机里只存在一个实例,这样的好处有:
某一些类的创建比较麻烦,省去了一个大型类的创建开销。
减少了new操作, 降低了系统内存使用的频率。
对于某些掌有控制权的类,如果创建了多个实例容易造成混淆,只有创建单例类才能保证单一控制类。
但是,单例类也有着自己的缺陷,下面是一个简单的单例类:
public class Singleton{
//私有化对象,防止被引用,同时赋值为null,延迟加载,提高效率
private Singleton singleton = null;
//私有化构造函数,防止实例化
private Singleton(){}
//通过该函数获取Singleton对象
public static Singleton getSingleton(){
if(singleton == null)
singleton = new Singleton();
return singleton;
}
}
该单例类能够基本满足我们的要求,但是,一旦涉及到了多线程,便会出现问题,这一部分涉及到了Java中的单例类,之后单独拿一篇文章来写这个知识点,这里便带过一下,贴一下基本完美的单例类:
static方式实现线程安全:
public class Singleton{
//私有化对象,防止被引用,同时赋值为null,延迟加载,提高效率
private static Singleton singleton = new Singleton();
//私有化构造函数,防止实例化
private Singleton(){}
//通过该函数获取Singleton对象
public static Singleton getSingleton(){
return Singleton.singleton;
}
}
内部类方式:
public class Singleton{
//私有化构造函数,防止实例化
private Singleton(){}
//内部类将singleton实例化
private class CreateSingleton{
private static Singleton singleton = new Singleton();
}
//通过该函数获取Singleton对象
public static Singleton getSingleton(){
return CreateSingleton.singleton;
}
}
创建Singleton对象时单独加synchronized关键字:
public class Singleton{
//私有化对象,防止被引用,同时赋值为null,延迟加载,提高效率
private Singleton singleton = null;
//私有化构造函数,防止实例化
private Singleton(){}
private static synchronized void syncInit(){
if(singleton == null)
singleton = new Singleton();
}
//通过该函数获取Singleton对象
public static Singleton getSingleton(){
if(singleton == null)
syncInit();
return singleton;
}
}
单例模式大抵如此,具体的之后另一篇文章详细介绍。
建造者模式和工厂模式也比较像,相对于建造者模式,工厂模式更适合简单一点的类,如果是很复杂的类,大量的功能组合起来,则需要用到建造者模式。
由于笔者在构造这模式这一部分理解不太透彻,看了许多代码也没有把握到精髓,这里便根据个人理解,在前面例子的基础上修改一番。
创建接口,同时在上面两个类的基础上再添加几个功能类:
public class ImageSender{
public void send(){
System.out.println("this is Image");
}
}
public class VideoSender{
public void send(){
System.out.println("this is Video");
}
}
添加FinalSender类:
public class FinalSender{
private ArrayList senders = new ArrayList();
public void addFunction(ArrayList senders){
this.senders = senders;
}
public void show(){
System.out.println("my function :");
for(int i = 0; i < senders.size(); i++){
System.out.println(senders.get(i).send(););
}
}
}
工厂类修改为构造者类:
public class Builder{
public FinalSender build(ArrayList functions){
FinalSender finalSender = new FinalSender();
finalSender.addFunction(functions);
return finalSender;
}
}
测试类:
public static void main(String[] args){
Builder builder = new Builder();
ArrayList functionA = new ArrayList();
ArrayList functionB = new ArrayList();
functionA.add(new EmailSender());
functionA.add(new ImageSender());
functionB.add(new MessageSender());
functionB.add(new VideoSender());
FinalSender ASender = builder.build(functionA);
FinalSender BSender = builder.build(functionB);
ASender.show();
BSender.show();
}
/*输出:my function :
this is Email
this is Image
my function :
this is Message
this is Video*/
构造者模式特点很明显,也是十分符合设计模式的原则,低耦合,低依赖。
当一个类需要重复创建时,同时又要保持效率,这时便可以考虑原型模式。原型模式实现了一个方法,用来克隆他自己。当直接创建对象的代价比较大时,则采用这种模式。
先看代码,克隆分为深克隆和浅克隆:
代码演示:
public class Prototype implements Cloneable{
//浅克隆
public Object clone() throws CloneNotSupportedException{
Prototype prototype = (Prototype)super.clone();
return prototype;
}
//深克隆
public Object deepClone(){
//将当前对象写入二进制流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//从二进制流读出对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos);
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
这里有一个问题,我看了网上许多代码,关于克隆的实现全是一个类实现Cloneable接口,之后其他类继承这个类,便能拥有克隆的功能,但是,如果原型类本身就拥有父类又该怎么办,而且根据合成复用原则,这里最好用接口实现克隆,但是接口的话,又不能提高clone代码的复用性,这一点有待研究。
适配器模式我在网上看了许多代码,一开始确实有许多不懂的地方,确实不知道这个适配器有什么作用,这里我尽可能讲清楚。适配器模式分为三种:类的适配器模式、对象的适配器模式、借口的适配器模式
核心思想:现在已有一个接口 i,一个类 A,现在想要 A 能实现接口 I,则新建一个类 B(Adapter适配器),B 继承 A 实现 i,现在 A a = new B(); 便能让 A 实现 i。代码:
//原有类A
public class A{
public void oldMethod(){
System.out.println("A's method");
}
}
//接口i
public interface I {
public void newMethod();
}
//适配器B
public class B extends A implements I{
public void newMethod(){
System.out.println("B's method");
}
}
//测试类
public static void main(String[] args){
A a = new B();
//I i = new B();
a.oldMethod();
a.newMethod();
}
/*输出:A's method
B's method*/
第二种大体上和第一种相似,第一种是将类转换以实现新的接口,第二种则是将对象转换以实现新的接口。代码:
只需修改适配器 B
public class B implements I{
private A a;
public B(A a){
this.a = a;
}
public void newMethod(){
System.out.println("B's method");
}
public void oldMethod(){
a.oldMethod();
}
}
//测试类
public static void main(String[] args){
A a = new A();
I i = new B(a);
i.oldMethod();
i.newMethod();
}
/*输出:A's method
B's method*/
//接口I
public interface I{
public void oldMethod();
public void newMethod();
}
//抽象类B
public abstract class B implements I {
public void oldMethod(){}
public void newMethod(){}
}
//实现类 A1
public class A1 extends B{
public void oldMethod(){
System.out.println("old method");
}
}
//实现类 A2
public class A2 extends B{
public void newMethod(){
System.out.println("new method");
}
}
//测试类
public static void main(String[] args){
A1 a1 = new A1();
A2 a2 = new A2();
A1.oldMethod();
A2.newMethod();
}
/*输出:old method
new method*/
小总结:
一开始,我感觉装饰模式和对象的适配器模式是基本一样的,后来发现了差别,这里记录一下:对象的适配器模式是让一个全新的对象通过适配器实现一个接口,以达到添加新功能的作用。而装饰模式则装饰者和被装饰者都需实现同一个接口,在此基础上,通过装饰者来往被装饰者中添加新功能。
一层装饰模式:
//定义接口I
public interface I(){
public void watch();
}
//定义实现类
public class A implements I{
public void watch(){
System.out.println("I can watch!");
}
}
现在想在A的基础上加强watch功能,添加装饰类:
public class Decorator implements I{
private A a;
public Decorator(A a){
this.a = a;
}
public void watch(){
System.out.println("I can watch!");
speak();
}
public void speak(){
System.out.println("I also can speak.");
}
}
//测试类:
public static void main(String[] args){
A a = new A();
I i = new Decorator(a);
a.watch();
i.watch();
}
/*输出:I can watch!
I can watch!
I also can speak.*/
由此可看出,想要在A的基础上添加新功能,可以无限制的增加装饰类,不过缺点也显而易见,多层装饰会比较复杂。
核心思想:为其他对象提供一种代理以控制这个对象的访问。
代理模式理解起来有点抽象,因为看上去和适配器模式、装饰模式长得好像,可能在实际应用中才能有更加深入的理解。
//定义接口
public interface I{
public void method();
}
//定义实现类(被代理类)
public class A implements I{
public void method(){
System.out.println("this is old method!");
}
}
//定义代理类
public class B implements I{
private A a;
public B(){
this.a = new A();
}
public void method(){
a.method();
System.out.println("and new method!");
}
}
//测试类
public static void main(String[] args){
I i = new B();
i.method();
}
/*输出:this is old method!
this is old method!
and new method!*/
注意事项:
缺点:
外观模式主要作用也是低耦合,将几个有关系的类拆分开,其关系放在另一个类中,这要要修改他们关系时只要修改一个关系类就行了,不过这个会违反开闭原则。
//接口
public interface I{
public void bagin();
}
//实现类
public class Teacher implements I{
public void bagin(){
System.out.println("teacher is born");
}
}
public class Student implements I{
public void bagin(){
System.out.println("student is born");
}
}
//关系类
public class A{
private Teacher teacher;
private Student student;
public A(){
teacher = new Teacher();
student = new Student();
}
public void born(){
teacher.born();
student.born();
}
}
//测试类
public static void main(String[] args){
A a = new A();
a.born();
}
/*输出:teacher is born
student is born*/
桥接模式主要用于将事物和其具体实现分开,最终目的:将抽象化与实现化解耦,使二者可以独立变化。离我们最近的便是连接数据库的JDBC桥,不管是换数据库类型还是换账号密码,要改动的地方其实特别少,原因便是JDBC提供了统一的接口。具体可以看代码
定义接口
public interface I {
public void method();
}
定义实现类:
public A implements I{
public void method(){
System.out.println("This is A!");
}
}
public B implements I{
public void method(){
System.out.println("This is B!");
}
}
定义抽象类,也就是桥,同时定义一个继承他的类:
public abstract class Bridge{
private I i;
public void method(){
I.method();
}
public I getI(){
return i;
}
public void setI(I i){
this.i = i;
}
}
public class myBridge extends Bridge{
public void method(){
getI.method();
}
}
测试类:
public class BridgeTest {
public static void main(String[] args) {
Bridge bridge = new MyBridge();
//调用第一个对象
I a = new A();
bridge.setSource(a);
bridge.method();
//调用第二个对象
I b = new B();
bridge.setSource(b);
bridge.method();
}
}
/*输出:This is A!
This is B!*/
桥接模式大致就是这样,下图则是JDBC大致的工作流程,放在这里以作参考。
组合模式可能用的比较少,先看代码
public class TreeNode {
private String name;
private TreeNode parent;
private Vector children = new Vector();
public TreeNode(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
//添加孩子节点
public void add(TreeNode node){
children.add(node);
}
//删除孩子节点
public void remove(TreeNode node){
children.remove(node);
}
//取得孩子节点
public Enumeration getChildren(){
return children.elements();
}
}
public class Tree {
TreeNode root = null;
public Tree(String name) {
root = new TreeNode(name);
}
public static void main(String[] args) {
Tree tree = new Tree("A");
TreeNode nodeB = new TreeNode("B");
TreeNode nodeC = new TreeNode("C");
nodeB.add(nodeC);
tree.root.add(nodeB);
System.out.println("build the tree finished!");
}
}
其实代码也能看得出,组合模式主要用于将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
享元模式的主要目的是实现对象的共享,即共享池。这一点其实都比较熟悉,比如数据库连接中的连接池、多线程当中的线程池,都属于这一类用法,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象。
这里看一下数据库连接池的代码:
public class ConnectionPool {
private Vector pool;
/*公有属性*/
private String url = "jdbc:mysql://localhost:3306/test";
private String username = "root";
private String password = "root";
private String driverClassName = "com.mysql.jdbc.Driver";
private int poolSize = 100;
private static ConnectionPool instance = null;
Connection conn = null;
/*构造方法,做一些初始化工作*/
private ConnectionPool() {
pool = new Vector(poolSize);
for (int i = 0; i < poolSize; i++) {
try {
Class.forName(driverClassName);
conn = DriverManager.getConnection(url, username, password);
pool.add(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/* 返回连接到连接池 */
public synchronized void release() {
pool.add(conn);
}
/* 返回连接池中的一个数据库连接 */
public synchronized Connection getConnection() {
if (pool.size() > 0) {
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
} else {
return null;
}
}
}
享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。