设计模式七大原则
编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好
- 1)代码重用性
- 2)可读性
- 3)可扩展性
- 4)可靠性
- 5)使程序呈现高内聚,低耦合的特性
1,单一职责
一个类只负责一项职责。降低类的复杂程度。提高类的可读性,可维护性。
2,接口隔离
客户端不需要依赖它不需要的接口,一个类对另一个类的依赖建立在最小的接口上。
3,依赖倒转
高层模块不应该依赖底层模块,两者都依赖抽象而不是依赖细节。中心思想是面向接口编程。
4,里氏替换
所有引用父类的地方必须能透明的使用其子类的对象,里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
5,开闭原则
一个软件实体应当对扩展开放(提供方),对修改关闭(使用方)。多去扩展方法,而不是修改方法。
6,迪米特法则
迪米特法则还有个更简单的定义:只与直接的朋友通信。
其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
7,合成复用
尽量使用对象组合,而不是继承来达到复用的目的。
单例模式
饿汉
public class Singleton {
// 随着类加载而立即加载
private static Singleton singleton = new Singleton ();
//私有化构造,防止new
private Singleton() {
}
//获取
public static Singleton getInstance() {
return singleton ;
}
}
懒汉
这个方式是我最喜欢的,所以只写他了,防止每次获取实例都要进行加锁,又保证了安全。
public class Singleton {
// 随着类加载而立即加载
private static volatile Singleton singleton ;
//私有化构造,防止new
private Singleton() {
}
//获取
public static Singleton getInstance() {
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton ();
}
}
}
return singleton ;
}
}
静态内部类
public class Singleton {
//私有化构造,防止new
private Singleton() {
}
private static class SingletonInstance {
private static final Singleton INSTANCE =new Singleton();
}
//获取
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
工厂模式
简单工厂
不再使用 new Object()形式
根据用户的选择条件来实例化相关的类
对于客户端来说,去除了具体的类的依赖
只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象
优点:可以对创建的对象进行一些 “加工” ,而且客户端并不知道,因为工厂隐藏了这些细节。
缺点:需要在方法里写很多与对象创建有关的业务代码,对象还不少的话,我们要在这个简单工厂类里编写很多个方法,每个方法里都得写很多相应的业务代码,而每次增加子类或者删除子类对象的创建都需要打开这简单工厂类来进行修改。这会导致这个简单工厂类很庞大臃肿、耦合性高,而且增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码也违反了开-闭原则。
具体实现如下
产品类
public interface Operation {
public double getResult(double numberA,double numberB) throws Exception;
}
具体产品类:加减乘除类
public class Add implements Operation{
//加法计算
public double getResult(double numberA, double numberB) {
return numberA + numberB;
}
}
public class Sub implements Operation{
// 减法计算
public double getResult(double numberA, double numberB) {
return numberA-numberB;
}
}
...
简单工厂
public class EasyFactory {
// 简单工厂,根据字符串创建相应的对象
public static Operation createOperation(String name) {
Operation operationObj = null;
switch (name) {
case "+":
operationObj = new Add();
break;
case "-":
operationObj = new Sub();
break;
case "*":
operationObj = new Mul();
break;
case "/":
operationObj = new Div();
break;
}
return operationObj;
}
}
客户端
public class Client {
public static void main(String[] args) throws Exception {
Operation add = EasyFactory.createOperation("+");
}
}
工厂模式
《图解设计模式》一书说道,父类(抽象工厂)决定实例的生产方式,但是不决定所要生产的具体的类,具体的生产处理交给子类处理。
这里多了一个抽象工厂
的概念。每次增加一个产品,都要增加其工厂类。
虽然需要增加代码,但是不用修改原有的工厂类代码,而是修改客户端代码,减少了耦合,增强程序可拓展。
具体实现如下:
抽象工厂:
public interface Factory {
public Operation createOperation() ;
}
具体工厂
// 加法类工厂
public class AddFactory implements Factory{
public Operation createOperation() {
System.out.println("加法运算");
return new Add();
}
}
// 减法类工厂
public class SubFactory implements Factory{
public Operation createOperation() {
System.out.println("减法运算");
return new Sub();
}
}
........
客户端
public class Client {
public static void main(String[] args) throws Exception {
// 使用反射机制实例化工厂对象,因为字符串是可以通过变量改变的
Factory addFactory=(Factory)Class.forName("org.factory.AddFactory").newInstance();
Factory subFactory=(Factory)Class.forName("org.zero01.SubFactory").newInstance();
// 通过工厂对象创建相应的实例对象
Operation add = addFactory.createOperation();
Operation sub = subFactory.createOperation();
}
}
抽象工厂
工厂模式:定义一个用于创建对象的借口,让子类决定实例化哪一个类
抽象工厂模式:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类
抽象工厂模式用友多个抽象产品类,而工厂模式只有一个。
原型模式
问题:
如果需要克隆一批完全相同的对象
传统的方式是我们可能会 :
将 目标对象的属性,一个一个赋值给新对象
原型模式:
原型模式原理结构图-uml类图
原型模式解决问题:
Student对象:注意实现Cloneable
接口
public class Student implements Cloneable{
private int id;
private String name;
protected int hight;
private int age;
private Room room;
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
//创建对象默认赋一些值
public Student() {
this.id = 2017;
this.name = "Mike";
this.age = 19;
this.hight=169;
System.out.println("Student对象创建完成");
}
//get set
}
Student的一个属性Room对象
public class Room {
String name;
int size;
……
}
测试1:
//学生1
Student student1 = new Student();
Room room = new Room();
room.setName("房间");
room.setSize(1000);
student1.setRoom(room);
//学生2
Student student2 = (Student) student1.clone();
System.out.println(student1);
System.out.println(student2);
System.out.println("student1和student2是同一个对象?"+(student1 == student2) );
结果:
- 注意,调用clone()时Student对象的 构造方法 并没有被调用第二次。
- student2是一个独立的对象,并不是student1的引用。
浅拷贝
我们在上面的测试代码基础上再加一句:
System.out.println("student1的房间和student2的房间不会是同一个吧?"+(student1.getRoom() == student2.getRoom()) );
结果:天哪,他们的房间是同一个对象!因为Room是引用对象,这个现象就是浅拷贝的结果了。
浅拷贝:
- 1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
- 3)浅拷贝是使用默认的clone()方法来实现。
拓展: String也是引用对象,那么改变student1的String属性会不会改变student2的值?
Student student1 = new Student();
Room room = new Room();
room.setName("房间");
room.setSize(1000);
student1.setRoom(room);
Student student2 = (Student) student1.clone();
//student1 决定改名
student1.setName("Jack");
System.out.println(student1);
System.out.println(student2);
结果可以看出,student2并没有随student1改名
因为String是final修饰类,,String类型的值发生改变时,他的指针是重新指向另一个新对象。
深拷贝
- 1)复制对象的所有基本数据类型的成员变量值
- 2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
- 3)深拷贝实现方式1:重写clone方法来实现深拷贝
- 4)深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)
重写clone的方法不过是这样:如果有其他新的引用对象那么还得在clone方法上拓展。(Room也需要实现Cloneable接口,并且重写clone方法)
@Override
public Object clone() {
Student student;
try {
student = (Student) super.clone();
//房间单独复制
student.setRoom( room.clone() );
return student;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
之前的测试再拿出来,就会发现他们的房间已经不是同一个了。
Student student1 = new Student();
Room room = new Room();
room.setName("房间");
room.setSize(1000);
student1.setRoom(room);
Student student2 = (Student) student1.clone();
System.out.println(student1);
System.out.println(student2);
System.out.println("student1和student2是同一个对象?"+(student1 == student2) );
System.out.println("student1的房间和student2的房间不会是同一个吧?"+(student1.getRoom() == student2.getRoom()) );
反序列化克隆对象,通过序列化和反序列化的方式来克隆对象,也可以得到上面的测试结果
@Override
public Object clone() {
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis =null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos =new ObjectOutputStream(bos);
//当前这个对象以对象流的方式输出
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
Student copyObj= (Student) ois.readObject();
return copyObj;
}catch (Exception e){
return null;
}finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
}catch (Exception e2){
}
}
小结: