前言
设计模式不属于技术:属于一种套路和公认的经验,是判断你代码写的是否好的一种标准。
总原则:开闭原则 OCP
对扩展开放,对修改关闭,即程序需要扩展,不修改原有代码,而是要求扩展原有代码,实现一个热插拔的效果,需要使用接口和抽象类。
1.单一职责原则:
每个类负责单一职责,如果不是就应该对类进行拆分。
2.里氏替换原则:
父类可以出现的地方,子类也一定能出现。
3.依赖倒转原则:(DI原则:也叫依赖注入原则)
面向接口编程,依赖抽象而不依赖具体,即不与具体类交互,而与具体类的上层接口交互
4.接口隔离原则:
把接口拆分,用小接口代替大接口,实现只实现需要的方法。
现在可以使用新语法来实现这种原则。
5.迪米特法则:最少知道原则
暴露出的功能,细节越少越好。
暴露必要的接口。
用户知道必须的东西就行了,不需要他们知道全部,主要是为了安全考虑。
6.合成复用原则
代替继承,尽量不要使用继承,使用对象。但是在某些时候也不能不用继承。
五种构建型模式
这五种设计模式分别为:
工厂模式
抽象工厂模式
建造者模式
单例模式
原型模式
单例模式
单例模式是非常常见的一种模式,某个对象全局只需要一个实例,就可以使用单例模式。
优点:
(1)避免对象重复创建,例如线程池、数据库连接池这种全局只需要创建一个的对象。
(2)避免了由于操作不同实例导致的逻辑错误
同时单例模式有两种:饿汉式、懒汉式
饿汉式
主要的特点:变量在声明的时候就初始化
为什么叫饿汉式,是因为创建的时候就初始化,就像饿汉一样。
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 单例模式中的饿汉模式
* 不允许外部直接创建实例
* 并且实例在类加载的时候就已经被实例化了
*/
public class HungerMode {
private static HungerMode simple = new HungerMode();
private HungerMode(){
}
public static HungerMode getSimple() {
return simple;
}
}
解释:
静态成员变量是属于类的,当类在初始化的时候,同时就初始化了simple,所以每次调用getSimple来获取静态变量simple。
这种模式是线程安全的,也就是说,无论你创建多少个线程,来调用这个类,simple变量也只会创建一次
懒汉式
主要特点:在使用的时候才初始化变量
懒汉式就如同,修理工修理东西一样,需要什么才从工具箱里面拿取什么。
这种模式是线程不安全的,所以需要考虑如何实现线程安全
先看一种线程不安全的模式,这种模式下多个线程同时初始化这个类,就会创建多个instance变量
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 懒汉式的单例模式
* 在获取的时候才进行实例化
* 线程不安全
*/
public class LazyMode {
private static LazyMode instance;
public static LazyMode getInstance(){
if(instance == null){
instance = new LazyMode();
return instance;
}else{
return instance;
}
}
}
解决这种情况很简单,使用加锁(synchronized),对初始化变量这段代码进行加锁,加锁之后自然就是线程安全的,
如以下代码:
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 懒汉式的单例模式
* 加锁之后就变成了线程安全
*/
public class LazyMode {
private static LazyMode instance;
public static LazyMode getInstance(){
synchronized (LazyMode.class){
if(instance == null){
instance = new LazyMode();
return instance;
}
}else{
return instance;
}
}
}
但是这样加锁,每次判断对象是否初始化的时候都要先获取锁,对效率肯定有影响,所以可以将加锁放在 if(instance == null)下面,如下面这段代码:
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 懒汉式的单例模式
* 加锁之后就变成了线程安全
*/
public class LazyMode {
private static LazyMode instance;
public static LazyMode getInstance(){
if(instance == null){
synchronized (LazyMode.class){
instance = new LazyMode();
return instance;
}
}else{
return instance;
}
}
}
这样一来,当判断是否需要初始化变量的时候,就不需要获取锁,就可以判断,只有当变量没有初始化的时候才获取锁,保证只初始化一次。
还可以使用静态内部类保证
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 实现懒汉式加载
* 根据类的加载中规则,使用哪个类就加载哪个类
* 运行的时候先加载LazyMode2
* 然后再加载Lazy类
* 因为内部类只有在使用的时候才加载
* 实现了懒汉加载
* 在加载clinit方法的时候,会保证多个线程正确的加锁、同步
* 从而保证了线程安全
*/
class LazyMode2{
private static class Lazy{
private static LazyMode2 instance = new LazyMode2();
}
private LazyMode2(){
}
public LazyMode2 getInStance(){
return Lazy.instance;
}
}
(1)如何实现懒加载:
首先初始化类的时候,会尽可能初始化所有的类,但是内部类加载不一样,内部类加载是在使用的时候才进行加载的,所以就实现了懒加载。
(2)如何保证线程安全:
在初始化类的时候,会保证多个线程正确的加锁和同步
懒汉式VS饿汉式
- 懒汉式:将加载是将从启动时延迟到了运行的时候,虽然启动时间大幅度减少了,但是会在运行的时候进行加载,例如支付宝、其他的网页应用,点击才请求数据
- 饿汉式:在启动的时候就完成加载,虽然启动时间有点长,但是用户体验会很好
建议:
对于构建不复杂的,加载完成后会立即使用的单例对象,推荐使用的单例对象,推荐使用饿汉式。对于构建时间较长,并不是所有此类都会用到的对象,推荐使用懒汉式。
原型模式
例如:喝奶茶,朋友买了一杯奶茶,我也想要一杯,如果像如下代码这样写
/**
* @author dark_souls https://blog.csdn.net/dark_souls
*/
public class Main {
public static void main(String[] args) {
MyTea a =new MyTea(10,"草莓奶茶",true);
MyTea b = a;
if(a ==b){
System.out.println("是同一杯奶茶");
}else{
System.out.println("是两杯奶茶");
}
}
}
这样就不是复制对象,而是将一个对象的引用赋值给b,相当于你们喝的是一杯奶茶
所以为了考虑达到生成两杯奶茶的效果,就要考虑使用以下的代码:
/**
*@author dark_souls https://blog.csdn.net/dark_souls
*/
public class Main {
public static void main(String[] args) {
MyTea a =new MyTea(10,"草莓奶茶",true);
MyTea b = a;
if(a == b){
System.out.println("是同一杯奶茶");
}else{
System.out.println("是两杯奶茶");
}
MyTea c = new MyTea(10,"草莓奶茶",true);
if(a == c){
System.out.println("是同一杯奶茶");
}else{
System.out.println("是两杯奶茶");
}
}
}
可见a和c不是一杯奶茶
同时在java中是有一个接口叫做Cloneable继承这个接口,可以调用官方的clone()方法。
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 可以使用原型模式进行对对象的复制
* 调用jdk自带的clone方法只会将基本类型复制一份,如果需要将对象也复制
* 需要修改clone方法
*/
public class MyTea implements Cloneable {
int money;
String type;
boolean ice;
public MyTea(int money, String type, boolean ice) {
this.money = money;
this.type = type;
this.ice = ice;
}
@Override
public MyTea clone() throws CloneNotSupportedException {
return (MyTea) super.clone();
}
}
调用父类的方法自然就可以进行复制
需要注意的是这个方法只是会复制基本类型,对于其他类型会直接引用,如果你需要完全的两个对象,需要修改clone方法,自己写例如下面这段代码
/**
* @author dark_souls https://blog.csdn.net/dark_souls
*/
public class MyOwnClone {
String name;
String type;
int price;
public MyOwnClone myclone(){
MyOwnClone myOwnClone = new MyOwnClone();
myOwnClone.name = name;
myOwnClone.type = type;
myOwnClone.price = price;
return myOwnClone;
}
}
工厂方法模式(分为工厂方法模式、抽象工厂方法模式)
例如我们需要创建一个苹果对象,需要知道苹果的构造方法,还需要知道苹果生长需要水是多少、阳光、种子,使用工厂方法模式则可以不需要知道苹果是如何种出来的,只需要和加工厂打交道,直接从加工厂获取苹果就可以了。主要是如果使用new来创建对象,每创建一个就相当于调用者多知道了一个类,不利于程序的松耦合,所以构建过程可以封装起来,工厂模式就是用于封装对象的设计模式。
简单工厂模式
/**
*@author dark_souls https://blog.csdn.net/dark_souls
*简单工厂方法
*/
public class FruitFactory {
public Fruit getFruit(String key){
if("apple".equals(key)){
int water = 5;
String sunshine = "晴天";
String seed = "苹果种子";
return new apple(water,sunshine,seed);
}else if( "bear".equals(key)){
int water = 7;
String sunshine = "阴天";
String seed = "梨子种子";
return new bear(water,sunshine,seed);
}
return null;
}
}
苹果的实体类:
/**
*@author dark_souls https://blog.csdn.net/dark_souls
*苹果的实体类,如果不手动传值就用默认值
*/
public class apple implements Fruit {
int water = 5;
String sunshine = "晴天";
String seed = "苹果种子";
public apple(int water,String sunshine,String seed){
this.water = water;
this.sunshine = sunshine;
this.seed = seed;
}
public apple(){
}
@Override
public void getWater() {
System.out.println("获取了"+water+"水");
}
@Override
public void getSeed() {
System.out.println("获得了"+seed);
}
@Override
public void getSunshine() {
System.out.println("需要"+seed);
}
@Override
public void sendFriut() {
System.out.println("成熟的苹果");
}
}
使用者调用方法:
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 只需要与factory打交道
* 告诉工厂需要什么水果就获得了什么样的水果
* 不需要关心水果所需要的阳光、水、种子
* 缺点:
*
* 1.一是如果需要生产的产品过多,此模式会导致工厂类过于庞大
* 承担过多的职责,变成超级类。当苹果生产过程需要修改时
* 要来修改此工厂,也就是说这个类不止一个引起修改的原因。
* 违背了单一职责原则
*
* 2.当要生产新的产品时,必须要工厂类中添加新的分支,开闭原则告诉我们
* 类该修改封闭,添加新的功能的时候,只需要添加新的类,而不是修改既有
* 的类,所以就违背了开闭原则
*/
public class Main {
public static void main(String[] args) {
FruitFactory fruitFactory = new FruitFactory();
Fruit apple = fruitFactory.getFruit("apple");
apple.sendFriut();
}
}
上述方法,使用了一个FruitFactory工厂类,将水果对象的创建都放在了工厂中进行。
缺点:
-
1.一是如果需要生产的产品过多,此模式会导致工厂类过于庞大
承担过多的职责,变成超级类。当苹果生产过程需要修改时
要来修改此工厂,也就是说这个类不止一个引起修改的原因。
违背了单一职责原则 -
2.当要生产新的产品时,必须要工厂类中添加新的分支,开闭原则告诉我们
类该修改封闭,添加新的功能的时候,只需要添加新的类,而不是修改既有
的类,所以就违背了开闭原则
工厂模式
由于上面两个问题的,工厂方法模式就是为了解决这个问题,将一个工厂方法变成了多个工厂方法,苹果有苹果工厂、梨子有梨子工厂
虽然这种方式使优点变少了、比如我必须得知道工厂名,但是这种方法也还是有优点的、例如我们依旧不用关注苹果是如何种出来的,只需要和工厂打交道就行,而且还符合开闭原则,需要添加新产品不需要修改原有的类,只需要添加新的类就行了。
以下是苹果工厂:
/**
* @author dark_souls https://blog.csdn.net/dark_souls
*/
public class AppleFactory {
public Fruit creat(){
return new apple();
}
}
用户调用类
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 第二个优点还存在不需要和知道如何生产水果,只需要和工厂打交道,
* 但是耦合度和不用工厂方法一样高,解决了简单工厂模式的两个弊端,
* 但工程类越来越多时,工厂方法不会变成超级类
* 符合单一职责原则
* 需要新产品的时候,只需要添加新的工厂就行,而不需要修改之前的类
* 开闭原则符合
*/
public class Main {
public static void main(String[] args) {
AppleFactory appleFactory = new AppleFactory();
Fruit apple = appleFactory.creat();
apple.sendFriut();
}
}
抽象工厂模式
抽象工厂模式是工厂方法模式的进一步优化,你可以提取出工厂接口
工厂接口如下,其他工厂都实现这个接口:
/**
*@author dark_souls https://blog.csdn.net/dark_souls
*/
public interface IFactory {
Fruit create();
}
如下为桃子工厂:
/**
*@author dark_souls https://blog.csdn.net/dark_souls
*/
public class BearFactory implements IFactory {
@Override
public Fruit create(){
return new bear();
}
}
用户调用方法:
/**
* @author dark_souls https://blog.csdn.net/dark_souls
* 应用:切换数据库讲SQL切换成orcal,客户端不会有察觉
* 发挥了开闭原则、依赖倒置原则
* 缺点:IFactory需要增加功能的时候,就会影响所有的具体工厂类
* 适合同工厂横向扩展,不适合纵向扩展(如:新增加功能)
*/
public class Main {
public static void main(String[] args) {
IFactory factory = new AppleFactory();
Fruit apple = factory.create();
apple.sendFriut();
}
}
抽象工厂方法模式,特点适合横向扩展需求,适合增加同类型的工厂,但是不适合修改工厂接口的抽象方法,如果修改了,就需要修改所有类的所有方法,不适合纵向扩展。
建造者模式
定义:将一个复杂的构建与它的表示相分离,使同样的构造过程课以创建不同的表示。
根据我的理解它和工厂模式区别在于工厂模式创造出来的产品是一样的,而创造者创建出来产品是一种产品的不同个体,都有微小区别。
比如要创建一个人,例子中我只设计了人的名字,头发,年龄,同时你还可以扩展很多例如:头、手、衣服,就创建出不同样貌的人。
构建者类
/**
*@author dark_souls https://blog.csdn.net/dark_souls
**/
public class Person {
private final String name ;
private final int age;
private String hair ;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", hair='" + hair + '\'' +
'}';
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getHair() {
return hair;
}
private Person(Builder builder){
this.name = builder.name;
this.age = builder.age;
this.hair = builder.hair;
}
public static class Builder{
private final String name;
private int age = 18;
private String hair = "秃头";
public Builder(String name){
this.name = name;
}
public Builder age(int age){
this.age = age;
return this;
}
public Builder hair(String hair){
this.hair = hair;
return this;
}
public Person build(){
return new Person(this);
}
}
}
用户调用方法的类
/**
* 建造者模式
*@author dark_souls https://blog.csdn.net/dark_souls
*/
public class Main {
public static void main(String[] args) {
Person man = new Person.Builder("张三")
.age(11)
.hair("寸头")
.build();
System.out.println(man.toString());
Person women = new Person.Builder("张蕾")
.age(20)
.hair("长发")
.build();
System.out.println(women.toString());
}
}
由上述代码可以看出,Pesron人这个类中有一个Builder类,专门用来构建人,如果只使用默认的build方法,就会返回一个自己传入名字18岁的秃头程序员。
同时可以看出Person的构造方式是私有的,也就意味着只有使用build构造内部类才能创建一个人,可选的数学通过链式调用的方法传入。