OOP的七大原则
OCP(Open-Closed Principle),开放封闭原则:软件实体应该扩展开放、修改封闭。
实现:合理划分构件,一种可变性不应当散落在代码的很多角落里,而应当被封装到一个对象里;一种可变性不应当与另一个可变性混合在一起。
DIP(Dependency Inversion Principle),依赖倒置原则:摆脱面向过程编程思想中高层模块依赖于低层实现,抽象依赖于具体细节。OOP中要做到的是,高层模块不依赖于低层模块实现,二者都依赖于抽象;抽象不依赖于具体实现细节,细节依赖于抽象。
实现:应该通过抽象耦合的方式,使具体类最大可能的仅与其抽象类(接口)发生耦合;程序在需要引用一个对象时,应当尽可能的使用抽象类型作为变量的静态类型,这就是针对接口编程的含义。
LSP(Liskov Substitution Principle),Liskov替换原则:继承思想的基础。“只有当衍生类可以替换掉基类,软件单位的功能不会受到影响时,基类才真正被复用,而衍生类也才能够在基类的基础上增加新的行为。”
ISP(Interface Insolation Principle),接口隔离原则:接口功能单一,避免接口污染。
实现:一个类对另外一个类的依赖性应当是建立在最小的接口上的。使用多个专门的接口比使用单一的总接口要好。
SRP(Single Resposibility Principle),单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。 如果一个类的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会抑止这个类完成其他职责的能力。
CARP(Composite/Aggregate Reuse Principle),合成/聚合复用原则:设计模式告诉我们对象委托优于类继承,从UML的角度讲,就是关联关系优于继承关系。尽量使用合成/聚合、尽量不使用继承。
实现:在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,以整合其功能。
LoD(Law Of Demeter or Principle of Least Knowledge),迪米特原则或最少知识原则:就是说一个对象应当对其他对象尽可能少的了解。即只直接与朋友通信,或者通过朋友与陌生人通信。
工厂模式
工厂模式的核心本质:
1、 实例化的对象不是用new, 而用工厂方法代替
2、 将选择的实现类, 创建对象统一管理和控制, 从而将调用者跟我们的实现类解耦
三种模式
1、 简单工厂模式
2、 工厂方法模式
3、 抽象工厂模式
遵守的OOP原则
1、开闭原则
2、 依赖倒置原则
3、 迪米特法则
简单工厂模式
简单工厂模式又称为静态工厂模式
建立一个简单的汽车接口
public interface Car {
void name();
}
实现这个接口 以宝马和奔驰为例子
public class BaoMa implements Car {
@Override
public void name() {
System.out.println("我叫宝马");
}
}
public class BenChi implements Car {
@Override
public void name() {
System.out.println(“我叫奔驰”);
}
}
正常情况下, 我们需要使用奔驰和宝马 这两个实现类需要用 new 实例化一个对象来使用这个方法
public class Main {
public static void main(String[] args) {
BaoMa baoMa = new BaoMa();
BenChi benChi = new BenChi();
baoMa.name();
benChi.name();
}
}
这样每调用一个新的汽车类型都需要new一个实例化对象,为了减少对new 的实例化对象的使用 ,用工厂方法来替代, 我们可以使用简单工厂模式,利用Java的static关键字,创建一个工厂类,利用变量name 来判断需要获取的是哪一个车型
public class CarFactory {
public static Car getCar(String name) {
Car car = null;
if (name.equals(“宝马”)) {
car = new BaoMa();
} else if (name.equals(“奔驰”)) {
car = new BenChi();
}
return car;
}
}
CarFactory.getCar(“宝马”).name();
CarFactory.getCar(“奔驰”).name();
工厂模式
虽然简单的工厂模式使用了工厂方法来调用实体类, 但是每当我们修改或者新增汽车的实现类时候都需要修改我们的工厂类,这是不满足开闭原则的。
我们可以将工厂定为接口,这样通过实现不同的工厂类来获取不同车型,并且再也不需要通过关键字来指定获取的车型(解耦)
public interface ICarFactory {
Car getCar();
}
实现不同的车型工厂
public class BaoMaFactory implements ICarFactory {
@Override
public Car getCar() {
return new BaoMa();
}
}
Car baoMa = new BaoMaFactory().getCar();
Car benChi = new BenChiFactory().getCar();
baoMa.name();
benChi.name();
并且 ,对于新增的汽车类型, 我们只需要实现相对应的工厂接口和车类接口, 降低了代码之间的耦合度。
但是也可以明显的感觉到,工厂模式相对于简单工厂模式代码的复杂度,代码量都有所增加,实际开发中常常选择简单工厂模式而不是工厂模式。
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模
式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供
了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象,从系统启动到终止,整个
过程只会产生一个实例。
饿汉式
在类加载的时候就初始化对象。
public class Hungry {
private Hungry(){
System.out.println(Thread.currentThread().getName()+“ok”);
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(Hungry::getInstance).start();
}
}
}
利用java的classloader机制避免了 多线程问题,而且不需要加锁,使用效率会很高,缺点加载的时候就初始化,当这个类所需要的的内存空间很大,会造成资源的浪费。
懒汉式(线程不安全)
和饿汉式不同,在需要使用这个类的时候在初始化
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName()+"ok");
}
private static Lazy LAZY;
public static Lazy getInstance() {
if (LAZY == null) {
LAZY = new Lazy();
}
return LAZY;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(Lazy::getInstance).start();
}
}
}
因为没有加锁,每次获取线程的运行结果都不一样。优点是,运行效率高(没加锁),缺点是多 线程环境不安全。
懒汉式(双重验证DCL)
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static Lazy LAZY;
public static Lazy getInstance() {
if (LAZY == null) {
synchronized (Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(Lazy::getInstance).start();
}
}
}
采用双重验证降低了加锁带来的性能开销,并且利用volatile关键字防止指令重排的问题
静态内部类
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return innerClass.HOLDER;
}
public static class innerClass {
public static final Holder HOLDER = new Holder();
}
}
在饿汉式的基础上,通过静态类延迟加载来实现懒加载,性能高,而且线程安全,实现难比DCL小
枚举类型
事实上, 上文提到的单例模式类型都是不安全,因为java有着强大的反射机制可以破坏单例唯一性。
以DCL为例:
package Singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
-
@author :beibei
-
@date :Created in 2020/4/20 9:30
-
@description :
*/
public class Lazy {
private Lazy() {
// System.out.println(Thread.currentThread().getName()+“ok”);
synchronized (Lazy.class) {
if (LAZY != null) {
throw new RuntimeException(“不要破坏单例”);
}
}
}private volatile static Lazy LAZY;
public static Lazy getInstance() {
if(LAZY == null){
synchronized (Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}public static void main(String[] args) throws Exception{
// for (int i = 0; i < 10; i++) {
// new Thread(Lazy::getInstance).start();
// }
Constructor declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy newInstance = declaredConstructor.newInstance();
Lazy instance = Lazy.getInstance();
System.out.println(newInstance);
System.out.println(instance);}
}
都可利用反射setAccessible等方式破坏私有类,破坏单例。
阅读反射的部分源码 可以看到这样一段代码
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException(“Cannot reflectively create enum objects”);
当类为枚举类型时,会抛出一个IllegalArgumentException错误,来防止反射对枚举类型类的单例破坏
public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance() {
return INSTANCE;
}
}
所以,首推使用反射进行单例模式的实现。
建造者模式
建造者提供了创建对象的最佳方式。
定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
主要作用: 在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
用户只需要给出复杂对象的类型和内容,建造者模式负责按照顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
指挥者(Director)直接和客户(Client)进行需求沟通,沟通后指挥者将客户创建产品的需求划分为各个部件的建造请求(Builder),将各个部件的建造请求委派到具体的建造者(ConcreteBuilder),各个具体建造者负责进行产品部件的构建,最终构建成具体产品(Product)。
Builder类
public abstract class Builder {
abstract void buildA();
abstract void buildB();
abstract void buildC();
abstract Product getProduct();
}
Director类
public class Director {
public Product build(Builder builder) {
builder.buildA();
builder.buildB();
builder.buildC();
return builder.getProduct();
}
}
Product类
public class Product {
private String BuildA;
private String BuildB;
private String BuildC;
public void setBuildA(String buildA) {
BuildA = buildA;
}
public void setBuildB(String buildB) {
BuildB = buildB;
}
@Override
public String toString() {
return "Product{" +
"BuildA='" + BuildA + '\'' +
", BuildB='" + BuildB + '\'' +
", BuildC='" + BuildC + '\'' +
'}';
}
public void setBuildC(String buildC) {
BuildC = buildC;
}
}
工人(createBuilder)类
public class Worker extends Builder {
private Product product;
public Worker() {
product = new Product();
}
@Override
void buildA() {
product.setBuildA("buildA");
System.out.println("构建buildA");
}
@Override
void buildB() {
product.setBuildB("buildB");
System.out.println("构建buildB");
}
@Override
void buildC() {
product.setBuildC("buildC");
System.out.println("构建buildC");
}
@Override
Product getProduct() {
return product;
}
}
导演类 Director 在 Builder模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类
同样的我们可以进行一些改造,利用链式编程的方式省略了Director类,用户直接操作。
public abstract class Builder {
abstract Builder buildA(String msg);
abstract Builder buildB(String msg);
abstract Builder buildC(String msg);
abstract Product getProduct();
}
package Builder.demo2;
/**
-
@author :beibei
-
@date :Created in 2020/4/21 0:11
-
@description :
*/
public class Worker extends Builder {private Product product;
public Worker() {
product = new Product();
}@Override
Builder buildA(String msg) {
product.setBuildA(msg);
return this;
}@Override
Builder buildB(String msg) {
product.setBuildB(msg);
return this;
}@Override
Builder buildC(String msg) {
product.setBuildB(msg);
return this;
}@Override
Product getProduct() {
return product;
}
}
public class Main {
public static void main(String[] args) {
Worker worker = new Worker();
worker.buildA(“beibei”).buildB(“beibei2”).buildC(“beibei3”);
Product product = worker.getProduct();
System.out.println(product.toString());}
}
虽然用户可以直接操作生成的产品但是不过这样的方式在build的产品类多的时候,可读性可见性差。
原型模式
克隆
Prototype
Cloneable
clone() 方法
浅克隆
以视频对象为例,有着名字(name),创建时间(createTime)属性,实现Cloneable接口
public class Video implements Cloneable {
private String name;
private Date createTime;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Video() {
}
public Video(String name, Date createTime) {
this.name = name;
this.createTime = createTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
Video v1 = new Video(“罗教授南波湾”, date);
System.out.println(“v1=>” + v1);
System.out.println(“v1 hashcode =>” + v1.hashCode());
// clone 克隆
Video clone = (Video) v1.clone();
System.out.println("clone =>" + clone);
System.out.println("clone hashcode=>" + clone.hashCode());
}
}
结果:
v1=>Video{name=‘罗教授南波湾’, createTime=Tue Apr 21 15:59:48 CST 2020}
v1 hashcode =>1735600054
clone =>Video{name=‘罗教授南波湾’, createTime=Tue Apr 21 15:59:48 CST 2020}
clone hashcode=>21685669
可以看到,虽然video的内容一样,但是他们的hashCode不一样,是不一样的对象。
我们修改date的时间
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
Video v1 = new Video(“罗教授南波湾”, date);
Video clone = (Video) v1.clone();
System.out.println(“v1=>” + v1);
System.out.println(“clone =>” + clone);
date.setTime(21113333);
System.out.println("-------------");
System.out.println("v1=>" + v1);
System.out.println("clone =>" + clone);
}
}
结果:
v1=>Video{name=‘罗教授南波湾’, createTime=Tue Apr 21 16:10:49 CST 2020}
clone =>Video{name=‘罗教授南波湾’, createTime=Tue Apr 21 16:10:49 CST 2020}
v1=>Video{name=‘罗教授南波湾’, createTime=Thu Jan 01 13:51:53 CST 1970}
clone =>Video{name=‘罗教授南波湾’, createTime=Thu Jan 01 13:51:53 CST 1970}
可以看见 ,不光光原本的v1对象的时间对象发生了改变,而且clone的对象也发生了该变。说明原本对象和clone对象都还共同依赖着 时间对象
所以,浅克隆仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
深克隆
浅克隆虽然实现了clone的目标,但是我们如果希望clone对象完全和原对象没关系,即上文中的克隆对象和时间对象不关联,修改了原对象时间也不会对克隆对象时间造成影响。那么我们只需要重写对象Cloneable的clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Video clone = (Video) super.clone();
clone.createTime = (Date) this.createTime.clone();
return clone;
}
同样的还可以使用序列化和反序列操作,生成完全新的对象,那样新对象和原本对象也无任何关系。
public static Object cloneObject(Object obj) throws IOException, ClassNotFoundException{
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(obj);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in =new ObjectInputStream(byteIn);
return in.readObject();