设计模式的7大原则
1.单一职责原则 SRP
2.里氏替换原则 LSP
3.依赖倒转原则 DIP
4.开闭原则 OCP
5.接口隔离法则 ISL
6.合成复合原则 CRP (CARP)
7.迪米特法则 LoD
————————————————————————————
(注意区分:单一职责 & 接口隔离)
记忆方式:
一个【单身】的【里氏】拿着一本【倒转】又【半开半闭】的书,在思考:
如何实现【接口分离】又实现【合成复用】?
这时脚底有一只【米老鼠】遛过。
OOP五大基本原则:SRP, OCP, LSP, DIP, ISP
——————————————————————————————————————————————
1. 单一职责原则 SRP (Single Responsibility Principle)
是指一个类的功能要单一,不能包罗万象。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。
2. 里式替换原则 LSP (Liskov Substitution Principle)
子类应当可以替换父类并出现在父类能够出现的任何地方。比如:公司搞年度晚会,所有员工可以参加抽奖,那么不管是老员工还是新员工,也不管是总部员工还是外派员工,都应当可以参加抽奖,否则这公司就不和谐了。
3.依赖倒置原则 DIP (Dependency Inversion Principle)
具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类: 而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能 造成循环依赖。一个常见的问题就是编译A模块时需要直接包含到B模块的cpp文件,而编译B时同样要直接包含到A的cpp文件。
4. 开放封闭原则 OCP (Open-Closed Principle)
一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。比如:一个网络模块,原来只服务端功能,而现在要加入客户端功能,那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码,这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。
5. 接口隔离原则 ISL (Interface Segregation Law)
模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来;
6. 合成复用原则 CRP (Composite Reuse Principle)
xxx
7. 迪米特法则 LoD (Law of Demeter)
xxx
——————————————————————————————————————————————
1.单一职责原则 SRP
Single Responsibility Principle
是指一个类的功能要单一,不能包罗万象。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。
唯有专注,才能保证对象的高内聚;唯有单一,才能保证对象的细粒度。
【单一职责
与接口隔离
的区别:】
单一职责
原则注重的是职责;而接口隔离
原则注重对接口依赖的隔离。
单一职责
原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;
而接口隔离
原则主要约束接口,主要针对抽象,针对程序整体框架的构建。
——————————————————
2.里式替换原则 LSP
Liskov Substitution Principle
子类应当可以替换父类并出现在父类能够出现的任何地方。比如:公司搞年度晚会,所有员工可以参加抽奖,那么不管是老员工还是新员工,也不管是总部员工还是外派员工,都应当可以参加抽奖,否则这公司就不和谐了。
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
它包含以下4层含义:
1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2.子类中可以增加自己特有的方法。
3.当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?
后果就是:你写的代码出问题的几率将会大大增加。
继承机制的缺点:
- 继承是入侵性的(只要继承,就必须拥有父类的所有属性与方法);
- 降低了代码的灵活性(子类拥有了父类的属性方法,会增多约束);
- 增强了耦合性(当父类的常量、变量、方法被修改时,必需要考虑子类的修改)。
违反里氏替换原则的反例子:
class Subtraction{
public int sub(int a,int b){
return a - b;
}
}
public class Client{
public static void main(String[] args){
Subtraction Sub = new Subtraction();
System.out.println("100-50 = "+Sub.sub(100,50));
System.out.println("100-80 = "+Sub.sub(100,80));
}
}
结果:
100-50 = 50
100-80 = 20
class Subtraction{
public int sub(int a,int b){
return a - b;
}
}
class Add extends Subtraction{
public int sub(int a,int b){
return a+b;
}
public int add(int a,int b){
return sub(a,b) + 100;
}
}
public class Client2{
@Test
public void test(){
Add add = new Add(){
System.out.println("100 - 50 = " add.sub(100,50));
System.out.println("100 - 80 = " add.sub(100,80));
System.out.println("100 + 20 + 100 = " add.add(100,20));
}
}
}
我们发现原来运行正常的相减功能发生了错误。原因就是类Add在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类Add的重写后的方法,造成原本运行正常的功能出现了错误。
在本例中,使用Subtraction类完成的功能,换成子类Add之后,发生了异常;
在实际编程中,我们常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大;
如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通谷的基类,原来的继承关系去掉,采用依赖、聚合、组合等关系替代。
——————————————————
3.依赖倒置原则 DIP
Dependency Inversion Principle
DIP是6大原则中最难以实现的原则,它是实现开闭原则的重要途径;DIP没有实现,就别想实现对扩展开放,对修改关闭;在项目中只要记住“面向接口编程”就基本上抓住了DIP的核心;
- 低层模块:不可分割的原子逻辑,可能会根据业务逻辑经常变化。
- 高层模块:低层模块的再组合,对低层模块的抽象。
- 抽象: 接口或抽象类(是底层模块的抽象,特点:不能直接被实例化)
- 与接口或抽象类对应的实现类:低层模块的具体实现(特点:可以直拉被实例化)
High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.
也可以说高层模块,低层模块,细节都应该依赖抽象;
在Java中,抽象就是接口或抽象类;所以依赖倒置原则在Java中的表现是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
- 接口或抽象类不依赖实现类;
- 实现类依赖接口或抽象类;
DIP的好处:
依赖倒置原则可以减少类间的耦合性,提高系统的稳定,降低并行开发引起的风险,提高代码的可读性和可维护性;
具体依赖抽象,上层依赖下层;
假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类: 而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能 造成循环依赖。一个常见的问题就是编译A模块时需要直接包含到B模块的cpp文件,而编译B时同样要直接包含到A的cpp文件。
public class Driver {
public void driver(Benz benz){
benz.run();
}
}
public class Benz {
public void run(){
System.out.println("Benz is running");
}
}
public class Client {
public static void main(String[] args) {
Driver tom = new Driver(); // 司机是tom
Benz benz = new Benz();
tom.driver(benz);
}
}
输出:Benz is running
如果司机买了一台BMW,想让它开起来?
结果是不能实现的,因为Driver类中的drive方法内:司机与奔驰是紧耦合的关系;
若要改为BMW,要更改司机类的public void drive(Benz benz)
(底层模块),这样不太应该;
这样更改:
public interface IDriver {
public void driver(ICar car);
}
public class Driver implements IDriver {
public void driver(ICar car){
car.run();
}
}
public interface ICar {
public void run();
}
public class Benz implements ICar {
public void run(){
System.out.println("Benz is running...");
}
}
public class BMW implements ICar {
public void run(){
System.out.println("BMW is running...");
}
}
public class Client {
public static void main(String[] args) {
Driver zac = new Driver();
Driver park = new Driver();
Benz benz = new Benz();
BMW bmw = new BMW();
zac.driver(benz); // zac开benz
park.driver(bmw); // park开bmw
}
}
输出:
Benz is running...
BMW is running...
——————————————————
4.开闭原则 OCP
Open-Closed Principle
一个模块在扩展性方面应该是开放的,而在更改性方面应该是封闭的;
比如:一个网络模块,原来只有服务端功能,而现在要加入客户端功能,那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码;
这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。
易错之处:
- 1.Novels构造器里必须写:
this.name = name
;不能写name = this.name
,否则空指针异常; - 2.Novel构造器必须是
public
的,否则空指针异常; - 3.get方法里,若是自动生成,容易写成
return null
return name
;
应该是return this.name
,否则空指针异常;
【例1】
public interface IBook {
public String getName();
public String getAuthor();
public int getPrice();
}
public class Novels implements IBook {
private String name;
private String author;
private int price;
public Novels(String name, String author, int price) {
this.name = name;
this.author = author;
this.price = price;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getAuthor() {
return this.author;
}
@Override
public int getPrice() {
return this.price;
}
}
public class BookStore {
private static final ArrayList<IBook> bookList = new ArrayList<>();
static{
bookList.add(new Novels("Asian History","J.K.",3200));
bookList.add(new Novels("European History","H.O'",8700));
}
public static void main(String[] args) {
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.CHINA);
format.setMaximumFractionDigits(2);
System.out.println("书店出售的书记录如下:");
for(IBook ooo:bookList){
System.out.println("书名:"+ooo.getName()
+" 作者:"+ooo.getAuthor()+" 售价:"+format.format(ooo.getPrice()/100)+"元");
}
}
}
输出:
书店出售的书记录如下:
书名:Asian History 作者:J.K. 售价:¥32.00元
书名:European History 作者:H.O' 售价:¥87.00元
现在书店决定,40元以上打8折,40元以下打9 折;如何解决这个问题呢?
第一个办法:修改接口
;
在IBook上新增加一个方法getOffPrice(),专门进行打折,所有实现类实现这个方法。
但是这样修改的后果就是实现类NovelBook要修改,BookStore中的main方法也修改,同时Ibook作为接口应该是稳定且可靠的,不应该经常发生变化,否则接口做为契约的作用就失去了效能,其他不想打折的书籍也会因为实现了书籍的接口必须打折,因此该方案被否定。
第二个办法:修改实现类
;
修改Novel 类中的方法,直接在getPrice()中实现打折处理,这个应该是大家在项目中经常使用的就是这样办法,通过class文件替换的方式可以完成部分业务(或是缺陷修复)变化,该方法在项目有明确的章程(团队内约束)或优良的架构设计时,是一个非常优秀的方法。
但是该方法还是有缺陷的,例如采购书籍人员也是要看价格的,由于该方法已经实现了打折处理价格,因此采购人员看到的也是打折后的价格,这就产生了信息的蒙蔽效果,导致信息不对称而出现决策失误的情况。该方案也不是一个最优的方案。
第三个办法:最优方案,通过扩展实现变化
;
增加一个子类 OffNovel,覆写getPrice方法,高层次的模块(也就是static静态模块区)通过OffNovel类产生新的对象,完成对业务变化开发任务。
接口IBooks
和Novels
都不用修改;
新写一个OffNovels extends Novels
;
把BookStore
里添加书籍信息的static块中new Novels
改成new OffNovels
;
// 要打折销售的书籍
public class OffNovels extends Novels {
public OffNovels(String name,String author,int price){
super(name,author,price);
}
@Override
public int getPrice(){
int selfPrice = super.getPrice();
int offPrice = 0;
if(selfPrice > 4000){
offPrice = selfPrice * 80 / 100;
}else{
offPrice = selfPrice * 90 / 100;
}
return offPrice;
}
}
public class BookStore {
private static final ArrayList<IBook> bookList = new ArrayList<>();
static{
bookList.add(new OffNovels("Asian History","J.K.",3200));
bookList.add(new OffNovels("European History","H.O'",8700));
}
public static void main(String[] args) {
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.CHINA);
format.setMaximumFractionDigits(2);
System.out.println("书店出售的书记录如下:");
for(IBook ooo:bookList){
System.out.println("书名:"+ooo.getName()
+" 作者:"+ooo.getAuthor()+" 售价:"+format.format(ooo.getPrice()/100)+"元");
}
}
}
输出:
书店出售的书记录如下:
书名:Asian History 作者:J.K. 售价:¥28.00元
书名:European History 作者:H.O' 售价:¥69.00元
归纳变化:
逻辑变化。只变化一个逻辑,而不涉及到其他模块:
比如原有的一个算法是ab+c,现在要求ab*c,可能通过修改原有类中的方法方式来完成,前提条件是所有依赖或关联类都按此相同逻辑处理。
子模块变化。一个模块变化,会对其他模块产生影响,特别是一个低层次的模块变化必然引起高层模块的变化,因此在通过扩展完成变化时,高层次的模块修改是必然的,刚刚的书籍打折处理就是类似的处理模块,该部分的变化甚至引起界面的变化。
可见视图变化。可见视图是提供给客户使用的界面,该部分的变化一般会引起连锁反应(特别是在国内做项目,做欧美的外包项目一般不会影响太大),如果仅仅是界面上按钮、文字的重新排布倒是简单,最司空见惯的是业务耦合变化,什么意思呢?一个展示数据的列表,按照原有的需求是六列,突然有一天要增加一列,而且这一列要跨度N张表,处理M个逻辑才能展现出来,这样的变化是比较恐怖的,但是我们还是可以通过扩展来完成变化,这就依赖我们原有的设计是否灵活。
【例2】
之前只有“Novels”一类的书;
要求添加新一类的数据:“ComputerBooks”,然后上架“Thinking In Java”一书;
新增ComputerBooks extends IBook
然后main函数类中,往列表添加时,添加ComputerBooks
的书信息即可;
计算机书籍接口以及计算机书籍类:
interface IConputerBook extends IBook{
public String getScope();
class ComputerBook implements IComputerBook{
private String name;
private int price;
private String author;
private int price;
public ComputerBook(String name,int price,String author,String scope){
this.name = name;
this.price = price;
this.author = author;
this.scope = scope;
}
public String getAuthor(){
return this.author;
}
public String getName(){
return this.name;
}
public String getPrice(){
return this.price;
}
public String getScope(){
return this.scope;
}
}
public class BookStore {
private static final ArrayList<IBook> bookList = new ArrayList<>();
static{
bookList.add(new OffNovels("Asian History","J.K.",3200));
bookList.add(new OffNovels("European History","H.O'",8700));
bookList.add(new ComputerBooks("Thinking in Java","K.N.",11000));
}
public static void main(String[] args) {
NumberFormat format = NumberFormat.getCurrencyInstance(Locale.CHINA);
format.setMaximumFractionDigits(2);
System.out.println("书店出售的书记录如下:");
for(IBook ooo:bookList){
System.out.println("书名:"+ooo.getName()
+"\t作者:"+ooo.getAuthor()+"\t售价:"+format.format(ooo.getPrice()/100)+"元");
}
}
}
输出:
书店出售的书记录如下:
书名:Asian History 作者:J.K. 售价:¥28.00元
书名:European History 作者:H.O' 售价:¥69.00元
书名:Thinking in Java 作者:K.N. 售价:¥110.00元
——————————————————
5.接口隔离法则 ISL
Interface Segregation Law
不要强迫用户依赖他们不需要使用的接口;
模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来;
比如:
设计了【犬类】,【宠物狗】(宠物狗 的方法有:跑,走
)
设计了【鸟类】,【宠物鸟】(宠物鸟 的方法有:飞,滑行
)
要求实现他们的方法;
他们各自使用的方法是不同的;
但是在违背了ISL的情况下,这些方法都会被写进【同一个接口类】中;
【宠物狗】和【宠物鸟】在implements
了此接口类的情况下,【必须要继承】他【不需要的方法】;
(尽管这些方法内容为空)
public interface I {
// 犬类 宠物狗:跑,走
// 鸟类 宠物鸟:飞,滑翔
public void run();
public void walk();
public void fly();
public void glide();
}
狗类
public class Dog {
public void run(I i){
i.run();
}
public void walk(I i){
i.walk();
}
}
宠物狗
public class PetDog implements I {
@Override
public void run() {
System.out.println("宠物狗会run");
}
@Override
public void walk() {
System.out.println("宠物狗会walk");
}
@Override
public void fly() {
}
@Override
public void glide() {
}
}
鸟类
public class Bird {
public void fly(I i){
i.fly();
}
public void glide(I i){
i.glide();
}
}
宠物鸟
public class PetBird implements I {
@Override
public void run() {
}
@Override
public void walk() {
}
@Override
public void fly() {
System.out.println("宠物鸟会fly");
}
@Override
public void glide() {
System.out.println("宠物鸟会glide");
}
}
public class Client {
public static void main(String[] args) {
Dog dog = new Dog();
dog.run(new PetDog());
dog.walk(new PetDog());
Bird bird = new Bird();
bird.fly(new PetBird());
bird.glide(new PetBird());
}
}
输出:
————————————————————————————————————
宠物狗会run
宠物狗会walk
宠物鸟会fly
宠物鸟会glide
应该把接口拆分!!!
应该把接口拆分!!!
应该把接口拆分!!!
应该把接口拆分!!!
应该把接口拆分!!!
————————
public interface I1 {
public void run();
public void walk();
}
public interface I2 {
public void fly();
public void glide();
}
public class Dog {
public void run(I1 i1){
i1.run();
}
public void walk(I1 i1){
i1.walk();
}
}
public class PetDog implements I1 {
@Override
public void run() {
System.out.println("宠物狗会run");
}
@Override
public void walk() {
System.out.println("宠物狗会walk");
}
}
public class Bird {
public void fly(I2 i2){
i2.fly();
}
public void glide(I2 i2){
i2.glide();
}
}
public class PetBird implements I2 {
@Override
public void fly() {
System.out.println("宠物鸟会fly");
}
@Override
public void glide() {
System.out.println("宠物鸟会glide");
}
}
public class Client {
public static void main(String[] args) {
Dog dog = new Dog();
dog.run(new PetDog());
dog.walk(new PetDog());
Bird bird = new Bird();
bird.fly(new PetBird());
bird.glide(new PetBird());
}
}
输出:
————————————————————————————————————
宠物狗会run
宠物狗会walk
宠物鸟会fly
宠物鸟会glide
——————————————————
6.合成复用原则 CRP
——————————————————
7.迪米特法则 LoD
——————————————————
Java的设计模式
设计模式分为三大类:
一.创建型模式:5种
【工厂方法模式】、【抽象工厂模式】、【单例模式】、建造者模式、原型模式;
二.结构型模式:7种
【适配器模式】、装饰器模式、【代理模式】、外观模式、桥接模式、组合模式、享元模式;
三.行为型模式:11种
策略模式(商城用)、模板方法模式、【观察者模式】、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式;
————————————————————————————————
提问:
- 什么是设计模式?
设计模式是世界上各种各样程序员用来解决特定设计问题的尝试和测试的方法。设计模式是代码可用性的延伸。
- 谈谈你知道的设计模式?请手动实现单例模式,Spring 等框架中使用了哪些模式?
大致按照模式的应用目标分类,设计模式可以分为
创建型模式、结构型模式、行为型模式。
-
创建型模式,是对对象创建过程的各种问题和解决方案的总结,包括各种
工厂模式
(Factory、Abstract Factory)、单例模式
(Singleton)、构建器模式
(Builder)、原型模式
(ProtoType)。 -
结构型模式,是针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验。常 见的结构型模式,包括
桥接模式
(Bridge)、适配器模式
(Adapter)、装饰者模式
(Decorator)、代理模式
(Proxy)、组合模式
(Composite)、外观模式
(Facade)、享元模式
(Flyweight)等。 -
行为型模式,是从类或对象之间交互、职责划分等角度总结的模式。比较常见的行为型模式 有
策略模式
(Strategy)、解释器模式
(Interpreter)、命令模式
(Command)、观察者模式
(Observer)、迭代器模式
(Iterator)、模板方法模式
(Template Method)、访问者模式
(Visitor)。 -
请列举出在JDK中几个常用的设计模式?
单例模式用于Runtime, Calendar和其他的一些类中。工厂模式被用于各种不可变的类如Boolean,像Boolean.valueOf方法。观察者模式被用于swing和很多的时间监听中。装饰器模式被用于多个java IO类。 -
什么是设计模式?你是否在你的代码里面使用过任何设计模式?
设计模式是世界上各种各样程序员用来解决特定设计问题的尝试和测试的方法。设计模式是代码可用性的延伸。 -
Java 中什么叫单例设计模式?请用Java 写出线程安全的单例模式
-
解释下什么是观察者模式?
观察者模式是基于对象的状态变化和观察者的通讯,以便他们作出相应的操作。简单的例子就是一个天气系统,当天气变化时必须在展示给公众的视图中进行反映。这个视图对象是一个主体,而不同的视图是观察者。 -
使用工厂模式最主要的好处是什么?在哪里使用?
工厂模式的最大好处是增加了创建对象时的封装层次。
如果你使用工厂来创建对象,之后你可以使用更高级和更高性能的实现来替换原始的产品实现或类,这不需要在调用层做任何修改。 -
举一个用Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?
装饰模式增加强了单个对象的能力。Java IO 到处都使用了装饰模式,典型例子就是Buffered 系列类如BufferedReader和BufferedWriter,它们增强了Reader和Writer对象,以实现提升性能的 Buffer 层次的读取和写入。 -
工厂模式与抽象工厂模式的区别?
首先来看看这两者的定义区别:
工厂模式:定义一个用于创建对象的借口,让子类决定实例化哪一个类
抽象工厂模式:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类
个人觉得这个区别在于产品,如果产品单一,最合适用工厂模式,但是如果有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。再通俗深化理解下:工厂模式针对的是一个产品等级结构 ,抽象工厂模式针对的是面向多个产品等级结构的。
再来看看工厂方法模式与抽象工厂模式对比:
————————————————————————————————————————
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。
所谓单例模式,就是采取一定方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法);
8种形式:
饿汉式(静态常量)
饿汉式(静态代码块)
懒汉式(线程不安全)
懒汉式(线程安全,同步方法)
懒汉式(线程安全,同步代码块)
双重检查
静态内部类
枚举(JDK1.5后)
描述:【保证一个类只有一个实例,并且提供一个访问该实例的全局访问点】
例如:代表JVM运行环境的Runtime类;
比如一台计算机上可连接多台打印机,但这台计算机上的程序只能有一个,这里就可以通过单例模式避免两个打印作业同时输入到打印机中,即在整个打印过程中只有一个打印程序的实例;
Spring中每个Bean都是单例的;
每个Servlet也是单例的;
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
- 提问:单例模式中为什么要延迟加载(延迟实例化)?
把对象的创建延迟到使用时创建,而不是实例化时创建,避免了性能的浪费;
当初创建一个对象的字对象开销大时,且有可能程序中用不到这个子对象,就提高了程序的启动速度;
—————————————————————
1.饿汉式(静态常量)
public class Test {
private Test(){}
public static final Test INSTANCE = new Test();
public static Test getInstance(){ // 容易忘记加static!!!
return INSTANCE;
}
}
————————————
public class Client {
public static void main(String[] args) {
Test t1 = Test.getInstance(); // 忘加static这里就无法调用方法!!!
Test t2 = Test.getInstance();
System.out.println(t1 == t2);
System.out.println("t1的HashCode = "+ t1.hashCode());
System.out.println("t2的HashCode = "+ t2.hashCode());
}
}
————————————
输出:
true
t1的HashCode = 1625635731
t2的HashCode = 1625635731
说明单例成功
优点:
在类加载时就完成了实例化,避免了线程同步问题;
缺点:
在类加载时就完成实例化,没有达到Lazy Loading的效果,如果从始至终未用过此实例,就会造成【内存的浪费】;
(instance在【类装载】时就实例化)在单例模式中大多数都是调用getInstance方法,这没问题;
但是导致【类装载】的原因有很多,因此不能确定有其他的方式(或其他静态方法)导致类装载,这是初始化instance就打不到Lazy loading的效果;
结论:可用,但是可能造成内存浪费;(确认适合用?那就用它)
2.饿汉式(静态代码块)
public class Test {
private Test(){}
public static final Test INSTANCE; // 新变化
static{ // 新变化
INSTANCE = new Test();
}
public static Test getInstance(){
return INSTANCE;
}
}
————————————
public class Client {
public static void main(String[] args) {
Test t1 = Test.getInstance(); // 忘加static这里就无法调用方法!!!
Test t2 = Test.getInstance();
System.out.println(t1 == t2);
System.out.println("t1的HashCode = "+ t1.hashCode());
System.out.println("t2的HashCode = "+ t2.hashCode());
}
}
————————————
输出:
true
t1的HashCode = 1625635731
t2的HashCode = 1625635731
优缺点和第一种是一样的;
3.懒汉式(线程不安全)
public class Test {
private static Test INSTANCE;
private Test(){}
public static Test getINSTANCE(){
if(INSTANCE == null){
INSTANCE = new Test();
}
return INSTANCE;
}
}
————————————
public class Client {
public static void main(String[] args) {
Test t1 = Test.getINSTANCE();
Test t2 = Test.getINSTANCE();
System.out.println(t1 == t2);
System.out.println(t1.hashCode());
System.out.println(t2.hashCode());
}
}
————————————
输出:
true
1625635731
1625635731
4.懒汉式(线程安全,同步方法)
————————————
输出:
5.懒汉式(线程安全,同步代码块)
————————————
输出:
6.双重检查
————————————
输出:
7.静态内部类
————————————
输出:
8.枚举(JDK1.5后)
————————————
输出:
实例
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
—————————————————————
public class Singleton {
private Singleton() {}
private static Singleton singleton = null;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) { // 以当前类的字节码对象为锁
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
—————————————————————
public class SingletonClass{
private static SingletonClass instance=null;
public static synchronized SingletonClass getInstance()
{
if(instance==null)
{
instance=new SingletonClass();
}
return instance;
}
private SingletonClass(){
}
}
——————————————————————————————
2、【懒汉式】
懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。
【懒汉式 线程不安全】
是否 Lazy 初始化:是
是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
——————————————————
【懒汉式 线程安全】
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:易
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
——————————————————
实例:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
//对第一行static的一些解释
// java允许我们在一个类里面定义静态类。比如内部类(nested class)。
//把nested class封闭起来的类叫外部类。
//在java中,我们不能用static修饰顶级类(top level class)。
//只有内部类可以为static。
public class Singleton{
//在自己内部定义自己的一个实例,只供内部调用
private static final Singleton instance = new Singleton();
private Singleton(){
//do something
}
//这里提供了一个供外部访问本class的静态方法,可以直接访问
public static Singleton getInstance(){
return instance;
}
}
————————————————————————————————————————
工厂模式
int prodNo;
class IProduct
{
public:
SimpleFactroy(int proNo)
{
this.prodNo=prodNo;
}
IProduct GetProduct()
{
switch(prodNo)
{
case 1:
return new ProductA();
case 2:
return new ProductB();
default:
return new ProductC();
}
}
}
//产品A
class ProductA: IProduct
{
//属性 ...
}
————————————————————————————————————————
————————————————————————————————————————
————————————————————————————————————————
————————————————————————————————————————
————————————————————————————————————————
设计模式 Design Pattern
设计模式是人们为软件开发中相同表征的问题,抽象出的可重复利用的解决方案。
目的:避免代码重用、程序大量修改,同时让代码更易于理解;有利于我们提高沟通、设计的效率和质量。
在某种程度上,设计模式已经代表了一些特定情况的最佳实践,同时也起到了软件工程师之间沟通的“行话”的作用,设计模式可以说是软件工程的基石。
————————————————————————————————
—————————————————
单例模式 Singleton
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。
即【某个类】在【整个系统中】只能有【一个实例对象】可被【获取和使用】的代码模式;
例如:代表JVM运行环境的Runtime类;
使用场景:无论如何都只要使用一个对象的时候;(线程池、缓存、硬件设备等)
比如一台【计算机】上可连接【多台打印机】,但这台计算机上的【打印程序】【只有一个】;
这里就可以通过单例模式【避免】【两个打印作业】【同时】输入到打印机中;
即在整个打印过程中只有一个打印程序的实例;
这就像多线程环境下(如Servlet环境),共享一个资源或操作者使用同一个对象;
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
【需要注意的是】:
单例模式保证系统中 (1).一个类只有一个实例,(2).单例类的构造函数必须为私有,(3).用静态变量保存该唯一实例,同时单例函数 (4).必须提供一个全局访问点,如get;
提问:单例模式中为什么要延迟加载(延迟实例化)?
把对象的创建延迟到使用时创建,而不是实例化时创建,避免了性能的浪费;
当初创建一个对象的字对象开销大时,且有可能程序中用不到这个子对象,就提高了程序的启动速度;
————————————————————————————
【先看一个违背单例模式的情况:一个类生成两个对象】
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Client {
public static void main(String[] args) {
Person per = new Person();
Person per2 = new Person();
per.setName("Jack");
per2.setName("Ted");
System.out.println(per.getName());
System.out.println(per2.getName());
}
}
输出:
Jack
Ted
单例模式应该保证只有一个对象!!
————————————————————————————————-
改造成:
单例 - 饿汉式
饿汉式单例是指【单例对象立即加载】在方法调用前,实例就已经创建好了。
在方法调用前,实例就已经创建好了(static立刻加载,不管你要不要,我都创建了对象)
描述:【线程安全,调用效率高,不能延时加载】(我们还是希望项目可以延时加载的)
1.【公共】【静态】【final】实例;
2.【私有】构造器
(强调这是一个单例,用final来保存 final 变量名大写)
构造器私有后,可以保证:
其他类就无法new新的实例;唯一实例能完全被本类控制;
【饿汉式在 单线程 / 多线程 都能保证单例】
直接创建对象,线程安全
直接实例化饿汉式(简洁直观)
JDK1.5后有简化:枚举式(最简洁)
静态代码块饿汉式(适合复杂实例化)
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private Person(){}
public static final Person INSTANCE = new Person();
public static Person getPerson(){
return INSTANCE;
}
}
public class Client {
public static void main(String[] args) {
Person per = Person.getPerson();
Person per2 = Person.getPerson();
per.setName("Jack");
per2.setName("Ted");
System.out.println(per.getName());
System.out.println(per2.getName());
}
}
输出:
Ted
Ted
即使main函数里创建了两个对象,输出仍能保证只有一个对象;
————————————————
其他例子:
饿汉式
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
private Singleton1(){}
}
public class Test1 {
public static void main(String[] args) {
Singleton1 s = Singleton1.INSTANCE;
System.out.println(s);
}
}
输出:
com.xxxx.collection.Singleton.Singleton1@60e53b93
————————————————
其他例子:
饿汉式 - 多线程
public class MySingleton {
public static final MySingleton INSTANCE = new MySingleton();
private MySingleton(){}
public static MySingleton getInstance(){
return INSTANCE;
}
}
public class MyThread extends Thread{
@Override
public void run(){
System.out.println(MySingleton.getInstance().hashCode());
}
public static void main(String[] args) {
MyThread[] mts = new MyThread[10];
for(int i = 0; i<mts.length;i++){
mts[i] = new MyThread();
}
for(int j = 0 ; j < mts.length; j++){
mts[j].start();
}
}
}
输出:
1607503813
1607503813
1607503813
1607503813
1607503813
1607503813
1607503813
1607503813
1607503813
1607503813
————————————————————————————
单例 - 懒汉式
【懒汉式单例】是指在方法调用获取实例时,才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。
线程不安全(单线程)
线程安全(多线程)
静态内部式类型(多线程)
【公共】【静态】【实例】不初始化!
【私有】构造器;
【静态】【同步】【get实例方法】{
如果 实例 == null
初始化实例
}
return 实例;
【同步】保证线程安全,但是每次都要同步,消耗一定资源;
模版:
public class MySingleton {
private static MySingleton INSTANCE;
private MySingleton(){}
public static synchronized MySingleton getINSTANCE(){
if(INSTANCE == null){
INSTANCE = new MySingleton();
}
return INSTANCE;
}
}
【单线程】下这么写:
public class Person {
// 懒汉式 单线程
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private Person(){}
private static Person person;
// 若未判断,结果还是会有两个对象
// 若第一次使用,person为空,可以创建对象;第二次使用时就只能返回person对象,不能新建了
// 坑:若写了if(person != null){ 会空指针异常
public static Person getPerson(){
if(person == null){
person = new Person();
}return person;
}
}
public class Client {
// 懒汉式 单线程
public static void main(String[] args) {
Person per = Person.getPerson();
Person per2 = Person.getPerson();
per.setName("Jack");
per2.setName("Ted");
System.out.println(per.getName());
System.out.println(per2.getName());
}
}
输出:
Ted
Ted
————————————————————————————
懒汉式在多线程下不能这么写(不加synchronized
),因为…
懒汉式的【多线程】要改造:
只需要在getPerson
前添加同步修饰:synchronized
;
public class Person {
// 懒汉式 多线程
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private Person(){}
private static Person person;
public static synchronized Person getPerson(){ // 多线程添加了synchronized
if(person == null){
person = new Person();
}return person;
}
}
————————————————
其他例子:
————————————————
其他例子:
——————————————————————————————————————————————
单例 - 双重检查
————————————————————————————
————————
工厂模式 Factory Pattern
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:创建过程在其子类执行。
————————
工厂 - 简单工厂
【简单工厂模式】又叫做【静态工厂方法模式】;
通过专门定义一个【类】来【负责创建其他类的实例】,【被创建的实例】通常都具有【共同父类】;
核心:用工厂模式代替new操作,来实例化对象;
实现了创建者和调用者的分离;
工厂 Creator
简单工厂的核心,他负责实现创建所有实例的内部逻辑,工厂类可以被外界直接调用,创建所需产品对象;
抽象 Product
简单工厂所创建的所有对象的父类,他负责描述所有实例所共有的公共接口;
具体产品 Concrete Product
简单工厂所创建的具体实例对象;
public interface Car {
public void run();
}
public class Audi implements Car {
@Override
public void run() {
System.out.println("Audi runs");
}
}
public class BYD implements Car {
@Override
public void run() {
System.out.println("BYD runs");
}
}
public class CarFactory {
public static Car createCar(String type){
if("Audi".equals(type)){
return new Audi();
}else if("BYD".equals(type)){
return new BYD();
}else{
return null;
}
}
}
意思是:若无Factory类,Client中实例化对象需要写:
Car c1 = new Audi();
那么消费者还需要关心车是怎么创建的(实例化),这样带来更多烦恼;
public class Client {
public static void main(String[] args) {
Car c1 = CarFactory.createCar("Audi");
Car c2 = CarFactory.createCar("BYD");
c1.run();
c2.run();
}
}
简单工厂没有完全满足OCP原则:
因为else return null 这里可能还要修改代码;
————————
工厂 - 工厂方法
Car接口;
Audi implements Car;
BYD implements Car;
保持一样…
不需要建立CarFactory了,为单个品牌建立单独的Factory,和CarFactory即可;
public interface CarFactory {
Car createCar();
}
public class AudiFactory implements CarFactory {
@Override
public Car createCar() {
return new Audi();
}
}
public class BYDFactory implements CarFactory {
@Override
public Car createCar() {
return new BYD();
}
}
public class Client {
public static void main(String[] args) {
Car c1 = new AudiFactory().createCar();
Car c2 = new BYDFactory().createCar();
c1.run();
c2.run();
}
}
这样对比简单工厂,好处是:
若新增一个品牌的车,无需再修改CarFactory里的代码了!!
直接新建一个新品牌的Factory即可!!!
扩展时更符合开闭原则,不需要修改原有代码!!
————————
工厂 - 抽象工厂
工厂中最复杂!
之前的 简单工厂 和 工厂方法 都是创建品牌下面的子类;若此时,我需要创建有多个接口的子类,这两种方法都无能为力了!
需要使用抽象工厂,针对有多个接口的情况;
这里引入一个【产品族】的概念:
抽象工厂是用来增加【产品族】的!!他对增加【单个产品】无能为力!!
public interface Engine {
void run();
void start();
}
class LuxuryEngine implements Engine{
@Override
public void run() {
System.out.println("高端发动机:跑");
}
@Override
public void start() {
System.out.println("高端发动机:启动");
}
}
class LowEngine implements Engine{
@Override
public void run() {
System.out.println("低端发动机:跑");
}
@Override
public void start() {
System.out.println("低端发动机:启动");
}
}
public interface Seat {
void message();
}
class LuxurySeat implements Seat{
@Override
public void message() {
System.out.println("高端座椅:可以按摩");
}
}
class LowSeat implements Seat{
@Override
public void message() {
System.out.println("低端座椅:不能按摩");
}
}
public interface Tyre {
void revolve();
}
class LuxuryTyre implements Tyre{
@Override
public void revolve() {
System.out.println("高端轮胎:转得快");
}
}
class LowTyre implements Tyre{
@Override
public void revolve() {
System.out.println("低端轮胎:转得慢");
}
}
public interface CarFactory {
Engine createEngine();
Seat createSeat();
Tyre createTyre();
}
public class LuxuryCarFactory implements CarFactory {
@Override
public Engine createEngine() {
return new LuxuryEngine();
}
@Override
public Seat createSeat() {
return new LuxurySeat();
}
@Override
public Tyre createTyre() {
return new LuxuryTyre();
}
}
public class LowCarFactory implements CarFactory {
@Override
public Engine createEngine() {
return new LowEngine();
}
@Override
public Seat createSeat() {
return new LowSeat();
}
@Override
public Tyre createTyre() {
return new LowTyre();
}
}
public class Client {
public static void main(String[] args) {
CarFactory factory = new LuxuryCarFactory();
Engine e = factory.createEngine();
e.run();
e.start();
}
}
输出:
高端发动机:跑
高端发动机:启动
用几种工厂模式设计一个计算机程序:
https://www.cnblogs.com/kubixuesheng/p/10353209.html
————————————————————————————————
代理模式 Proxy
1.静态代理
2.动态代理(JDK代理)(接口代理)
3.CGLIB代理(子类代理)(有时被归类到:动态代理)
为一个对象提供一个替身,以控制这个对象的访问,即通过代理对象访问目标对象;
代理对象,比如:代理老师
被代理对象(目标对象),比如:老师
这样的好处是:
可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能;
被代理的对象可以是:【远程对象】、【创建开销大的对象】或【需要安全控制的对象】;
代理模式有不同的形式,主要三种:【静态代理】、【动态代理】(JDK代理、接口代理)、【CGLIB代理】(可以在内存中动态创建对象,不需要像另两个一样实现接口,属于动态代理);
————————————————
静态代理
需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承相同父类;
例子:Teacher生病了,我们需要找一个代理老师来代替他上课;
1.定义接口:ITeacherDao
2.目标对象TeacherDAO
实现刚才的接口;
3.使用静态代理方法,就需要在【代理对象】TeacherDAOProxy
中也实现ITeacherDAO
;
4.调用时,通过调用【代理对象】的方法来调用【目标对象】;
5.【特别注意】代理对象和目标对象要实现相同的接口,然后通过相同方法来调用目标对象的方法;
关键点:
- 一个
老师接口
:
teach()
老师类
:
implements他,@Override
;代理老师
:
implements他;
而且聚合老师类
:public TeacherDAO target
;写构造器,参数是target
;
@Override
写上重要业务逻辑;Client
:
实例化两个老师类,关键在于要把老师实例对象填入代理老师的实例化参数中,再调用代理老师的核心方法;
public interface ITeacherDAO {
void teach();
}
public class TeacherDAO implements ITeacherDAO {
@Override
public void teach() {
System.out.println("老师~~授课~~");
}
}
public class TeacherDAOProxy implements ITeacherDAO {
private ITeacherDAO target; // 通过接口来聚合目标对象
// 构造器
public TeacherDAOProxy(ITeacherDAO target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("事物:代理开始,完成某些操作");
target.teach();
System.out.println("事物:代理提交。。。");
}
}
//执行的是代理对象的方法,代理对象再去调用目标对象的方法
public class Client {
public static void main(String[] args) {
TeacherDAO teacherDAO = new TeacherDAO();
TeacherDAOProxy teacherDAOProxy = new TeacherDAOProxy(teacherDAO);
teacherDAOProxy.teach();
}
}
输出:
事物:代理开始,完成某些操作
老师~~授课~~
事物:代理提交。。。
优点:
在不修改目标对象功能的前提下,能通过代理对象对目标功能扩展;
(代理老师类中:@Override上课方法那里,(核心方法不动)可以轻松调用其他方法,不用修改老师的上课方法)
缺点:
因为代理对象要和目标对象实现一样的接口,所以会有很多代理类;
一旦接口增加方法,目标对象和代理对象都要维护;
(接口除了teach()
,还增加其他方法时,那么老师和代理老师都要修改代码)
————————————————
动态代理
1.代理对象不需实现接口;但是【目标对象】必须要实现接口!!!否则不能叫动态代理;
2.代理对象的生成利用了JDK的API,动态在【内存中】构建【代理对象】;(不是目标对象!!!)
3.动态代理也叫JDK代理
、接口代理
;
JDK中生成代理对象的API:
1.代理类所在包:java.lang.reflect.Proxy
2.JDK实现代理只需要用newProxyInstance
方法(JDK的核心方法),但是该方法需要接口三个参数,完整写法:
static Object newProxyInstance(ClassLoader loader,class<?>[]
interfaces,InvocationHandler h)
ClassLoader:【目标对象】使用的加载器
Interfaces:【目标对象】要实现的接口类型,用范型确认类型
InvocationHandler:执行目标对象的方法,触发事件处理器的方法(把当前目标对象的方法作为参数)
通过newProxyInstance
生成的 代理对象 调用 目标对象 的方法;
ITeacherDAO
public interface ITeacherDAO {
void teach();
}
TeacherDAO
public class TeacherDAO implements ITeacherDAO {
@Override
public void teach() {
System.out.println("老师执行了上课方法");
}
}
ProxyFactory
public class ProxyFactory {
// 维护一个目标对象
private Object target;
// 构造器对target初始化
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象生成一个代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理开始");
Object returnVal = method.invoke(target,args);
return returnVal;
}
});
}
}
重写的invoke()方法的三个参数又是什么呢?顾名思义
proxy
:动态生成的代理对象
method
:目标方法的实例
args
:目标方法的参数
Client
public class Client {
public static void main(String[] args) {
// 创建一个目标对象
TeacherDAO target = new TeacherDAO();
// 给目标对象创建代理对象,可转成ITeacherDAO
ITeacherDAO proxyInstance = (ITeacherDAO)new ProxyFactory(target).getProxyInstance();
System.out.println("proxyInstance = "+proxyInstance);
proxyInstance.teach();
}
}
输出:
JDK代理开始
proxyInstance = com.xxxxxx.DynamicProxy.TeacherDAO@d716361
JDK代理开始
老师执行了上课方法
Client改成:
System.out.println("proxyInstance = "+proxyInstance.getClass());
输出:
proxyInstance = class com.sun.proxy.$Proxy0
————————————————
CGLIB代理
涉及底层,不多说,只需要明白通过CGLIB可以返回【目标对象】的【代理对象】
————————————————
1.静态代理
和JDK代理
都要求【目标对象】要实现一个接口;(静态代理要求两个对象都实现接口)
但有时候【目标对象】只是一个单独的对象,没有实现任何接口,这个时候就可以使用:
【CGLIB代理】(目标对象子类代理)
2.【CGLIB代理】也叫【子类代理】:他会在内存中构建一个子类对象,从而实现对目标对象实现代理;
有些地方也把CGLIB代理归类到【动态代理】;
3.他广泛地被许多AOP框架使用,例如Spring AOP,实现方法拦截;
- 提问:如何在AOP中选择代理模式?
1.目标对象需要实现接口?JDK代理;
2.目标对象不需要实现接口?CGLIB代理;
4.CGLIB的底层通过使用字节码处理框架ASM来转换字节码并生成新的类;
参考:AOP文章
https://blog.csdn.net/weixin_42915286/article/details/90210359
【面试提问】:AOP的底层是怎么来实现的?
2部分:JDK动态代理;CGLIB代理;
如果问到底用的哪一个?
【面试提问】:在编译时期的织入还是运行时期的织入?
两者都是在 运行时 织入;
【面试提问】:初始化时期织入还是获取对象时期织入?
通过源码分析,可以知道是在初始化时期织入;
注意!!!
1.需要引入CGLIB的JAR包;
2.在内存中构建子类,注意【代理对象】不能为final
,否则报错IllegalArgumentException
;
3.如果【目标对象】的方法为final
、static
,那么就不会被拦截,即不会执行【目标对象】额外的业务方法;
import net.sf.cglib.proxy.MethodInterceptor;
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
public class TeacherDAO {
public void teach(){
System.out.println("老师在上课...CGLIB 不需要实现接口");
}
}
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 返回一个target对象的代理对象
// 涉及底层,不多说,只需要明白通过CGLIB可以返回目标对象的代理对象
public Object getProxyInstance(){
//1.创建工具类
//2.设置父类
//3.设置回调函数
//4.创建子类对象,即代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this); // 回调他自己
return enhancer.create();
}
// 重写intercept拦截方法,调用【目标代理】的方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB代理模式开始");
Object returnVal = method.invoke(target,args);
System.out.println("CGLIB代理模式提交");
return returnVal;
}
}
public class Client {
public static void main(String[] args) {
TeacherDAO target = new TeacherDAO();
TeacherDAO proxyInstance = (TeacherDAO)new ProxyFactory(target).getProxyInstance();
proxyInstance.teach();
}
}
输出:
CGLIB代理模式开始
老师在上课...CGLIB 不需要实现接口
CGLIB代理模式提交
从上面的代码看到,cglib的使用流程还是很清晰明了,各种参数顾名思义,和jdk的区别不大。
生成的代理对象直接被该类引用,与我们认知的基于继承的动态代理没冲突。
不过这种基于继承的方式的缺点,最明显的一点就是final修饰的类无法使用。
————————————————————————————————
策略模式 Strategy Pattern
1.策略模式:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户;
2.此算法体现了几个设计原则:
(1).第一:把变化的代码从不变的代码中分离出来;
(2).第二:针对接口编程而不是具体类(定义了策略接口);
(3).第三:多用组合/聚合,少用继承(客户通过组合方式使用策略);
————————————————————————
public interface Strategy {
public int calc(int a,int b);
}
public class AddStrategy implements Strategy {
@Override
public int calc(int a, int b) {
return a + b;
}
}
public class SubtractStrategy implements Strategy {
@Override
public int calc(int a, int b) {
return a - b;
}
}
public class Environment {
private Strategy strategy;
public Environment(Strategy strategy) {
this.strategy = strategy;
}
public int calculate(int a,int b){
return strategy.calc(a,b);
}
}
public class Test {
public static void main(String[] args) {
Environment environment = new Environment(new SubtractStrategy());
int result = environment.calculate(100,8);
System.out.println(result);
}
}
输出:
92
————————————————————————
————————————————————————
要求:编写一个鸭子项目;
1.有各种鸭子种类(野鸭、北京鸭、水鸭等),有各种鸭子行为(叫、飞行);
2.显示各种鸭子的信息;
使用策略模式前,需要新建一个接口,定义所有方法,然后所有鸭都implements他,实现自己可能不需要的方法;
【使用了策略模式后】
野鸭:飞得好;
北京鸭:飞得差;
玩具鸭:不会飞;
public interface FlyBehavior {
public void fly();
}
public class FlyBehavior_Good implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞得好");
}
}
public class FlyBehavior_Bad implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞得差");
}
}
public class FlyBehavior_No implements FlyBehavior{
@Override
public void fly() {
System.out.println("不会飞!!!");
}
}
public abstract class Duck {
public abstract void describe();
//聚合策略接口
FlyBehavior flyBehavior;
public Duck() {}
public void fly(){
if (flyBehavior != null){
flyBehavior.fly();
}
}
// 改进:动态调整
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
}
public class Duck_Wild extends Duck{
@Override
public void describe() {
System.out.println("这是野鸭");
}
public Duck_Wild() {
flyBehavior = new FlyBehavior_Good();
}
}
public class Duck_Peking extends Duck{
@Override
public void describe() {
System.out.println("这是北京鸭");
}
public Duck_Peking() {
flyBehavior = new FlyBehavior_Bad();
}
}
public class Duck_toy extends Duck{
@Override
public void describe() {
System.out.println("这是玩具鸭");
}
public Duck_toy() {
flyBehavior = new FlyBehavior_No();
}
}
public class Client {
public static void main(String[] args) {
Duck_Wild wild = new Duck_Wild();
wild.describe();
wild.fly();
// 改进:动态调整(野鸭不能飞)
System.out.println("——————————动态修改———————————");
wild.setFlyBehavior(new FlyBehavior_No());
wild.fly();
}
}
输出:
这是野鸭
飞得好
——————————动态修改———————————
不会飞!!!
————————————————————————
还有一个例子讲解:
https://www.bilibili.com/video/av57936239/?p=143
————————————————————————————————
观察者模式 Observer Pattern
项目:微信公众号的订阅
需求:
- 1.公众号可以随时更新标题和文章,发布给订阅者;
- 2.需要设计开放型API,便于订阅者能接入公众号的数据;
- 3.提供【标题】、【文章】的接口;
- 4.新文章数据更新后,必须实时通知给订阅者;
普通方式(非观察者模式)
类似微信公众号的订阅业务:
微信个人用户可以选择订阅哪家公众号;
- 1.Subject:公众号的编辑
- 2.Observer:公众号的订阅者
Subscriber
有一个订阅用户
定义三个属性;
post方法:由公众号主动调用此方法来【推送】新文章;
display方法:给该用户显示新文章;
public class Subscriber {
private String title;
private String article;
public void post(String title,String article) {
this.title = title;
this.article = article;
display();
}
public void display(){
System.out.println(" *** New title: " + title);
System.out.println(" *** New article: " + article);
}
}
OfficialAccount
核心类;公众号编辑通过他更新最新文章;
public class OfficialAccount {
private String title;
private String article;
private Subscriber subscriber;
public OfficialAccount(Subscriber subscriber){
this.subscriber = subscriber;
}
public String getTitle() {
return title;
}
public String getArticle() {
return article;
}
public void setData(String title,String article){
this.title = title;
this.article = article;
dataChange();
}
public void dataChange(){
subscriber.post(getTitle(),getArticle());
}
}
Client
public class Client {
public static void main(String[] args) {
Subscriber subscriber = new Subscriber();
OfficialAccount officialAccount = new OfficialAccount(subscriber);
officialAccount.setData("广东省发布新政","内容详情");
System.out.println(" ———————— New Article ———————— ");
officialAccount.setData("今年最强台风杀到","内容详情");
}
}
*** New title: 广东省发布新政
*** New article: 内容详情
———————— New Article ————————
*** New title: 今年最强台风杀到
*** New article: 内容详情
此普通方式有无发现什么问题?
如果要添加一个订阅者?
1.要新增一个订阅者类;
2.要修改公众号类:
说明了不利于维护,也不是动态加入(因为要重启程序);
————————————————————————
观察者模式
类似微信公众号的订阅业务:
微信个人用户可以选择订阅哪家公众号;
- 1.Subject:业务主体(公众号)
- 2.Observer:观察者(订阅者)
Interface:Subject
registerObserver()
:订阅
removeObserver()
:移除、取消订阅
notifyObservers()
:主动推送或者主动获取;
Interface:Observer
update()
:接受消息;
可以看出,观察者模式是多对一的设计方案(Observer多,Subject一);
public interface Observer {
public void update(String title,String article);
}
public class Subscriber implements Observer {
String title;
String article;
@Override
public void update(String title, String article) {
this.title = title;
this.article = article;
display();
}
public void display(){
System.out.println(" ***** 我是第一个订阅者 ***** ");
System.out.println("New Title: "+title);
System.out.println("New Article: "+article);
}
}
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}
public class OfficialAccount implements Subject {
String title;
String article;
private ArrayList<Observer> observers;
public OfficialAccount(){
observers = new ArrayList<>();
}
// 为了在此类创建时,就立刻创建ArrayList
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObserver() {
for (int i =0 ; i<observers.size() ; i++){
observers.get(i).update(this.title = title,this.article = article);
}
}
public void setData(String title,String article){
this.title = title;
this.article = article;
dataChange();
}
public void dataChange(){
notifyObserver();
}
}
public class Client {
public static void main(String[] args) {
OfficialAccount officialAccount = new OfficialAccount();
Subscriber subscriber = new Subscriber();
Subscriber2 subscriber2 = new Subscriber2();
officialAccount.registerObserver(subscriber);
officialAccount.registerObserver(subscriber2);
officialAccount.setData("投资3.5亿,海心沙体育场将变身","详情");
}
}
————————————————————————
怎么添加一个订阅者?
1.新建一个订阅者类;
2.客户端:添加新订阅者类的实例,把他添加进注册方法;
OfficialAccount业务逻辑不用改动;
public class Subscriber2 implements Observer {
String title;
String article;
@Override
public void update(String title, String article) {
this.title = title;
this.article = article;
display();
}
public void display(){
System.out.println(" ***** 我是第二个订阅者 ***** ");
System.out.println("New Title: "+title);
System.out.println("New Article: "+article);
}
}
public class Client {
public static void main(String[] args) {
OfficialAccount officialAccount = new OfficialAccount();
Subscriber subscriber = new Subscriber();
Subscriber2 subscriber2 = new Subscriber2(); // NEW
officialAccount.registerObserver(subscriber);
officialAccount.registerObserver(subscriber2); // NEW
officialAccount.setData("投资3.5亿,海心沙体育场将变身","详情");
}
}
***** 我是第一个订阅者 *****
New Title: 投资3.5亿,海心沙体育场将变身
New Article: 详情
***** 我是第二个订阅者 *****
New Title: 投资3.5亿,海心沙体育场将变身
New Article: 详情
————————————————————————
怎么移除一个订阅者?
客户端:比如移除第一个订阅者;
officialAccount.removeObserver(subscriber);
————————————————————————
观察者模式的好处:
1.以集合的模式来管理用户Observer,包括注册、移除、通知;
2.如此,增加一个观察者就不需要修改核心类OfficialAccount的代码,遵守了开闭原则;
————————————————————————
再写一个观察者模式例子:
区别是没有在单个类中定义属性;
public interface IObservable {
public void addObserver(IObserver observer);
public void removeObserver(IObserver observer);
public void notifyObservers(String message);
}
public class Someone implements IObservable {
private List<IObserver> observerList;
public Someone() {
observerList = new ArrayList<>();
}
@Override
public void addObserver(IObserver observer) {
observerList.add(observer);
}
@Override
public void removeObserver(IObserver observer) {
observerList.remove(observer);
}
@Override
public void notifyObservers(String message) {
for(IObserver observer:observerList){
observer.handleNotify(message);
}
}
}
public interface IObserver {
public void handleNotify(String message);
}
public class FirstObserver implements IObserver {
@Override
public void handleNotify(String message) {
System.out.println("一号观察者接收到消息: "+ message);
}
}
public class SecondObserver implements IObserver {
@Override
public void handleNotify(String message) {
System.out.println("二号观察者接收到消息: "+ message);
}
}
public class Client {
public static void main(String[] args) {
FirstObserver firstObserver = new FirstObserver();
SecondObserver secondObserver = new SecondObserver();
Someone someone = new Someone();
someone.addObserver(firstObserver);
someone.addObserver(secondObserver);
someone.notifyObservers("Let's GO!!!");
}
}
一号观察者接收到消息: Let’s GO!!!
二号观察者接收到消息: Let’s GO!!!
————————————————————————————————
监听器设计模式(属观察者的一种实现)
【监听器设计模式】其实就是观察者设计模式的一种实现,他不是23种设计模式之一;
- 被监听对象 - 事件源;
- 【多了一个对象】:事件源的状态发生了改变时,被定义了一个对象:事件;
(观察者的例子里定义的是一两个String) - 对监听器的通知:触发监听器;
————————————————————————
Event
// 定义事件接口:增删改查
// 声明事件类型
// 获取事件源对象
// 获取时间类型
public interface IEvent {
String SAVE_EVENT = "save event";
String FIND_EVENT = "find event";
String MODIFY_EVENT = "modify event";
String REMOVE_EVENT = "remove event";
IEventSource getEventSource();
String getEventType();
}
// 事件类
public class Event implements IEvent{
// 事件源
private IEventSource eventSource;
// 事件源执行的方法名
private String methodName;
public Event(IEventSource eventSource, String methodName) {
this.eventSource = eventSource;
this.methodName = methodName;
}
@Override
public IEventSource getEventSource() {
return eventSource;
}
@Override
public String getEventType() {
String eventType = null;
if (methodName.startsWith("save")){
eventType = SAVE_EVENT;
}else if(methodName.startsWith("remove")){
eventType = REMOVE_EVENT;
}else if (methodName.startsWith("modify")){
eventType = MODIFY_EVENT;
}else if (methodName.startsWith("find")){
eventType = FIND_EVENT;
}else{
eventType = "Illegal event type!!!";
}
return eventType;
}
}
EventSource - Some
// 事件源接口
// 为事件源注册监听器
// 触发监听器(观察者模式里叫:notifyObservers)
public interface IEventSource {
public void setListener(IListener listener);
public void triggerListener(IEvent event);
}
// 事件源
public class Some implements IEventSource {
private IListener listener;
@Override
public void setListener(IListener listener) {
this.listener = listener;
}
@Override
public void triggerListener(IEvent event) {
listener.handle(event);
}
// 下面是事件源类真正的业务逻辑,监听器监听的是这些业务方法的执行
public void saveStudent(){
System.out.println("向DB中插入了一条数据");
IEvent event = new Event(this,"saveStudent");
this.triggerListener(event);
}
public void removeStudent(){
System.out.println("向DB中插入了一条数据");
IEvent event = new Event(this,"removeStudent");
this.triggerListener(event);
}
public void modifyStudent(){
System.out.println("向DB中插入了一条数据");
IEvent event = new Event(this,"modifyStudent");
this.triggerListener(event);
}
public void findStudent(){
System.out.println("向DB中插入了一条数据");
IEvent event = new Event(this,"findStudent");
this.triggerListener(event);
}
}
Listener
// 处理事件
public interface IListener {
public void handle(IEvent event);
}
public class Listener implements IListener{
@Override
public void handle(IEvent event) {
String eventType = event.getEventType();
if (Event.SAVE_EVENT.equals(eventType)){
System.out.println("事件源执行的操作为:save");
}else if(Event.REMOVE_EVENT.equals(eventType)){
System.out.println("事件源执行的操作为:remove");
}else if(Event.MODIFY_EVENT.equals(eventType)){
System.out.println("事件源执行的操作为:modify");
}else if(Event.FIND_EVENT.equals(eventType)){
System.out.println("事件源执行的操作为:find");
}
}
}
public class Client {
public static void main(String[] args) {
Listener listener = new Listener();
Some some = new Some();
some.setListener(listener);
some.saveStudent();
// some.removeStudent();
// some.modifyStudent();
// some.findStudent();
}
}
返回:
向DB中插入了一条数据
事件源执行的操作为:save
————————————————————————
————————————————————————————————