1.单例模式
概述: 在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式. 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
public class Window {
//饿汉式单例, 类在初始化时就会加载, 就会初始化对象,只有一份
//不会存在任何线程安全的问题
private static Window window = new Window();
private Window(){
}
public static Window getWindow(){
return window;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Window.getWindow());
}).start();
}
}
}
(2)懒汉式单例
该模式在类加载时不创建对象, 只有在使用时才创建对象, 这时生成对象的数量需要我们来控制, 所以会存在线程安全问题。
以下是懒汉式单例的代码演示:
当有多个线程同时进入到第一个if中时, 第一个线程去创建对象, 后来获得锁的线程就不会再创建对象, 所以这里利用了两个if, 也就是双重检索+synchronized
public class Window {
private static Window window;
private Window(){
}
//懒汉式单例,在类加载的时候不创建对象,在使用时创建对象
//这时,生成的对象的数量需要我们自己来控制
//懒汉式单例会出现线程安全问题:
// 在多线程访问时,可能会出现多个线程同时进入到if,就会创建出多个对象
//如何解决?
// 1.给方法加锁,但是效率太低,一次只能有一个线程进入
// 2.给代码块加锁,双重检索+synchronized
public static Window getWindow(){
if(window==null){
synchronized (Window.class){
if(window==null){
window=new Window();
}
}
}
return window;
}
}
懒汉式单例双重检索+volatile
在我们创建对象时, 编译后的汇编指令码正常的执行顺序如下:
此时指令可能发生重排序,先把半成品对象引用地址赋给引用变量 t
线程 1暂停执行,线程 2进入到 cpu执行,引用变量t 不为空,指向的是半成品对象.
所以说如果在检索时用if(t != null)来进行判断就会出现问题, 虽然不为空, 但是是一个半成品对象。
2.工厂模式(Factory Pattern)
(1)简单工厂模式
简单工厂模式并不是 23 种设计模式之一,因为它并不符合开闭原则。主要目的是为了引出工厂方法模式,适合产品子类比较少的、创建操作比较简单的情况。
由一个工厂类根据传入的参数(一般是字符串参数),动态决定应该创建哪一个产品子类的实例,并以父类形式返回。
以下是代码演示(汽车厂造汽车):
首先创建一个Car接口
public interface Car {
void run();
}
其次创建几个具体的汽车(奥迪、宝马)
奥迪汽车:
public class Aodi implements Car{
@Override
public void run() {
System.out.println("奥迪汽车行驶");
}
}
宝马汽车:
public class Bmw implements Car{
@Override
public void run() {
System.out.println("宝马汽车行驶");
}
}
再创建一个造汽车的工厂
/*
汽车工厂
*/
public class CarFactory {
public static Car createCar(String name){
if(name.equals("aodi")){
Aodi aodi = new Aodi();
//aodi.
return aodi;
}
if(name.equals("bmw")){
return new Bmw();
}
if(name.equals("bc")){
return new BC();
}
return null;
}
}
最后创建一个Test类进行造汽车
public class Test {
public static void main(String[] args) {
Car bmw = CarFactory.createCar("bmw");
Car aodi = CarFactory.createCar("aodi");
bmw.run();
aodi.run();
}
}
根据我们传入的内容造相应的汽车, 所以很明显这是不满足开闭原则的, 每添加一个种类的汽车都需要更改原代码。
- 客户端不负责对象的创建,而是由专门的工厂类完成;
- 客户端只负责对象的调用,实现了创建和调用的分离,降低了客户端代码的难度;
- 如果增加和减少产品子类,需要修改简单工厂类,违背了开闭原则如果产品子类过多,会导致工厂类非常的庞大,违反了高内聚原则,不利于后期维护.
- 所有的产品子类都有同一个父类(或接口),属于同一个产品系列产品子类比较少的、创建操作比较简单。
(2)工厂方法模式
代码演示如下(还是同样的造汽车):
先创建一个Car接口
public interface Car {
void run();
}
其次创建一个工厂接口
public interface CarFactory {
Car createCar();
}
再创建两个具体的汽车(奥迪和宝马),分别都实现Car
奥迪:
public class Aodi implements Car {
@Override
public void run() {
System.out.println("奥迪汽车行驶");
}
}
宝马:
public class Bmw implements Car {
@Override
public void run() {
System.out.println("宝马汽车行驶");
}
}
再创建具体的工厂(奥迪工厂和宝马工厂), 分别实现工厂接口(CarFactory)
奥迪工厂
public class AodiFactory implements CarFactory{
@Override
public Car createCar() {
return new Aodi();
}
}
宝马工厂
public class BmwFactory implements CarFactory{
@Override
public Car createCar() {
return new Bmw();
}
}
最后创建一个Test类进行测试(造车)
public class Test {
public static void main(String[] args) {
CarFactory aodicarFactory = new AodiFactory();
Car aodi = aodicarFactory.createCar();
aodi.run();
CarFactory bmwcarFactory = new BmwFactory();
Car bmw = bmwcarFactory.createCar();
bmw.run();
CarFactory bcf = new BCFactroy();
Car bc = bcf.createCar();
bc.run();
}
}
- 客户端不负责对象的创建,而是由专门的工厂类完成;
- 客户端只负责对象的调用, 实现了创建和调用的分离,降低了客户端代码的难度;
- 若增加和减少产品子类,不需修改工厂类,只增加产品子类和工厂子类,符合开闭原则即使产品子类过多, 不会导致工厂类的庞大,利于后期维护
代码演示如下:
比如一个工厂可以造一类的所有产品, 这里我们创建车和手机
首先分别创建Car和Phone接口
public interface Car {
void run();
}
public interface Phone {
void call();
}
其次创建一个抽象工厂接口(可以造手机和汽车)
public interface AbstractFactory {
Car getCar();
Phone getPhone();
}
咋子分别创建具体的工厂(奥迪工厂和宝马工厂)
奥迪工厂(造奥迪汽车和奥迪手机)
public class AodiFactory implements AbstractFactory{
@Override
public Car getCar() {
return new AodiCar();
}
@Override
public Phone getPhone() {
return new AodiPhone();
}
}
宝马工厂(造宝马汽车和宝马手机)
public class BmwFactory implements AbstractFactory{
@Override
public Car getCar() {
return new BmwCar();
}
@Override
public Phone getPhone() {
return new BmwPhone();
}
}
public class BmwFactory implements AbstractFactory{
@Override
public Car getCar() {
return new BmwCar();
}
@Override
public Phone getPhone() {
return new BmwPhone();
}
}
紧接着就是分别创建两个奥迪品牌的Car和Phone以及两个宝马品牌的Car和Phone, 这里就不创建了。
最后一个Test类进行测试
public class Test {
public static void main(String[] args) {
AbstractFactory aodiFactory = new AodiFactory();
Car aodiCar = aodiFactory.getCar();
Phone aodiphone = aodiFactory.getPhone();
aodiCar.run();
aodiphone.call();
AbstractFactory bmwFactory = new BmwFactory();
Car bmwCar = bmwFactory.getCar();
Phone bmwPhone = bmwFactory.getPhone();
bmwCar.run();
bmwPhone.call();
}
}
3.原型模式
- singleton: 单例bean 一个类,创建一个对象,spring启动时,就创建单例对象
- prototype: 原型的,多例 每次获取时,都会创建一个新的对象
4.代理模式
比如在spring框架中的aop(面向切面编程):
在不修改代码的情况下,如果扩展一个新的功能(与业务没有直接联系),不修改代码可以实现, 底层实现就是通过一个代理对象来实现对扩展功能的调用。
- 1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 2. 代理对象可以扩展目标对象的功能;
- 3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
代理模式的实现可以分为静态代理和动态代理
静态代理
静态代理模式的特点,代理类接受一个 Subject 接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。
要求目标类,必须实现一个接口, 在运行时,使用反射,动态获取接口信息,调用某个方法,获取到你要调用的方法,最终执行invoke()方法。将生成的代理对象,调用的方法,参数等信息传递过来, 这样就实现了代理对象,可以代理任何一个目标类, 但是要求所代理的目标类,必须要实现一个接口。