设计模式
设计模式(Design Patterns)是软件开发人员在软件开发过程中面临的一般问题的解决方案。设计模式不是一种工具或产品,而是一种描述在特定环境下,如何解决问题或设计软件的思路和方法的模板。
1.1 设计模式的必要性
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
正确使用设计模式具有以下优点。
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
1.2 设计模式的分类
Java 中一般认为有 23 种设计模式,我们不需要所有的都会,但是其中常用的几种设计模式应该去掌握。下面列
出了所有的设计模式。需要掌握的设计模式我单独列出来了,当然能掌握的越多越好。
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模
式、状态模式、访问者模式、中介者模式、解释器模式。
1.3 单例模式
单例模式的主要有以下角色:
- 单例类。只能创建一个实例的类.
- 访问类。使用单例类
1.3.1 饿汉式
class Student{
//构造私有化
private Student(){
}
//创建静态本类对象---饿汉式
//private static Student stu=new Student();
//提供静态方法---返回本类对象---饿汉式
public static Student Singleton(){
return stu;
}
}
说明:
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
1.3.2 懒汉式(双重检查锁)
class Student{
//构造私有化
private Student(){
}
//创建静态本类对象---懒汉式
private static Student stu;
//提供静态方法---返回本类对象---懒汉式
public static Student Singleton(){
//第一次判断,多线程进入;后续线程不需要进入直接返回
if (stu==null){
//多线程抢锁,解决线程安全
synchronized (Student.class){
//第一次抢到锁的线程,创建对象,同时第一次其余多线抢到锁后不需要创建
if (stu==null){
stu=new Student();
}
}
}
return stu;
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用 `volatile` 关键字, `volatile` 关键字可以保证可见性和有序性。(在成员属性上加上volatile关键字)
1.4 工厂模式
工厂模式(Factory Pattern) 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
简单来说就是: 我们创建一个工厂类,然后当调用者想要创建一个对象时,只需要告诉工厂类即可,由工厂类去创建对象,调用者无需知道是如何创建的,也不用自己去创建。
再形象点说就是: 比如我们需要购买一批电脑,我们只要把我们的需求告诉电脑工厂,电脑工厂就会帮我们把这批电脑做好,而我们是不用自己去做这个电脑的,也不用我们自己去知道这个电脑是怎么做出来的,这就是工厂模式。
工厂模式分为简单工厂模式,工厂方法模式,抽象工厂模式,它们都属于设计模式中的创建型模式。其主要功能都是帮助我们把对象的实例化部分抽取了出来,目的是降低系统中代码耦合度,并且增强了系统的扩展性。
1.4.1 简单工厂模式
简单工厂模式只是将不同对象的创建操作进行了一层简单的封装,其实也就是把不同对象的创建操作全都单独放到一个类中,这个类就成为了简单工厂类;当我们需要某个对象时,只需把我们的需求告诉这个简单工厂类,然后由这个简单工厂类根据我们的需求去创建对应的对象即可。
1、创建手机抽象类
public abstract class Phone {
public abstract void lookTime();
public abstract void play();
}
2、创建继承了手机抽象类的,具体的不同品牌的手机的实体类
public class HuaweiPhone extends Phone{
@Override
public void lookTime() {
System.out.println("华为");
}
@Override
public void play() {
System.out.println("打游戏");
}
}
public class VivoPhone extends Phone{
@Override
public void lookTime() {
System.out.println("vivo");
}
@Override
public void play() {
System.out.println("vivo玩");
}
}
3、 创建一个手机工厂
public class PhoneFactory {
public static Phone createPhone(String type){
if (type.equals("Huawei")){
return new HuaweiPhone();
} else if (type.equals("vivo")) {
return new VivoPhone();
}
return null;
}
}
4、测试:使用工厂生产不同品牌手机并使用
public class Test {
public static void main(String[] args) {
Phone phone=PhoneFactory.createPhone("Huawei");
phone.lookTime();
}
}
缺点: 扩展性差,违背了开闭原则(开闭原则指的是:软件实现应该对扩展开放,对修改关闭)。新增产品时,需要修改工厂类。
1.4.2 工厂方法模式
在工厂方法模式中,我们的工厂类下面还有很多子工厂类,我们需要的对象是由这些子工厂类来创建的。其实就是改进了简单工厂模式,因为当我们需要一个新产品时,只需要扩展一个新的子工厂类即可,而不用去修改原有的代码,这样就符合了开闭原则。
1、创建一个手机抽象类(同上)
2、创建继承了手机抽象类的,具体的不同品牌的手机的实体类(同上)
3、创建一个手机抽象工厂类
public abstract class AbstractFactory {
public abstract Phone createPhone();
}
4、创建手机抽象工厂类的子类(根据不同的手机品牌)
public class huaweiFactory extends AbstractFactory{
@Override
public Phone createPhone() {
return new huawei();
}
}
public class vivoFactory extends AbstractFactory{
@Override
public Phone createPhone() {
return new vivo();
}
}
5、测试:使用不同子工厂生产不同品牌电脑并使用
public class Test01 {
public static void main(String[] args) {
Phone phone=new huaweiFactory().createPhone();
phone.play();
Phone phone1=new vivoFactory().createPhone();
phone1.play();
}
}
优点: 扩展性好,符合了开闭原则,新增一种产品时,只需增加改对应的产品类和对应的工厂子类即可。比如样例实现中,当我们需要一个苹果电脑时,只需要去新增一个苹果电脑类和一个苹果工厂类即可,而无需去修改原有的代码。符合单一职责原则,每个工厂只负责一种产品,而不是由一个工厂去生成所有商品。
缺点: 当我们新增产品时,还需要提供对应的工厂类,系统中类的个数将会成倍增加,相当于增加了系统的复杂性
1.4.3 抽象工厂模式
1、 创建各种类型的手机抽象类
public abstract class Phone {
abstract void use();
}
public abstract class miniPhone {
abstract void use();
}
2、 创建具体的不同品牌的各种类型的手机实体类
public class huawei extends Phone{
@Override
void use() {
System.out.println("正常华为手机");
}
}
public class minihuawei extends miniPhone{
@Override
void use() {
System.out.println("华为迷你手机");
}
}
public class vivo extends Phone{
@Override
void use() {
System.out.println("vivo正常手机");
}
}
public class minivivo extends miniPhone{
@Override
void use() {
System.out.println("vivo迷你");
}
}
3、创建一个手机的抽象工厂类
public abstract class AbatractPhoneFactory {
public abstract Phone producePhone();
public abstract miniPhone produceminiPhone();
}
4、 创建手机抽象工厂类的子类(根据不同的手机品牌)
public class huaweiFactory extends AbatractPhoneFactory{
@Override
public Phone producePhone() {
return new huawei();
}
@Override
public miniPhone produceminiPhone() {
return new minihuawei();
}
}
public class vivoFactory extends AbatractPhoneFactory{
@Override
public Phone producePhone() {
return new vivo();
}
@Override
public miniPhone produceminiPhone() {
return new minivivo();
}
}
5、 测试:使用不同子工厂创建不同品牌的不同种类的手机
public class Test01 {
public static void main(String[] args) {
AbatractPhoneFactory abatractPhoneFactory=new huaweiFactory();
Phone phone = abatractPhoneFactory.producePhone();
phone.use();
miniPhone miniPhone = abatractPhoneFactory.produceminiPhone();
miniPhone.use();
}
}
//运行结果
正常华为手机
华为迷你手机
1.5 代理模式
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
考虑生活中一个常见的例子,客户想买房,房东有很多房,提供卖房服务,但房东不会带客户看房,于是客户通过中介买房。
代理模式的结构
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构。
代理模式的主要角色如下。
- 抽象主题(Subject)类(业务接口类):通过接口或抽象类声明真实主题和代理对象实现的业务方法,服务端需要实现该方法。
- 真实主题(Real Subject)类(业务实现类):实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
1.5.1 静态代理
静态代理在使用的时候,需要定义接口或者父类,被代理对象(目标对象)与代理对象一起实现共同的接口或者是继承相同的父类。
//接口方法--卖房子
public interface RentHouse {
public void RentHouse();
}
//真实主题--房东
public class Landlord implements RentHouse{
@Override
public void RentHouse() {
System.out.println("看房子");
}
}
//代理类--中介
public class Agent implements RentHouse{
private Landlord landlord;
public Agent(Landlord l){
this.landlord=l;
}
@Override
public void RentHouse() {
System.out.println("请客户喝茶");
landlord.RentHouse();
System.out.println("送走客户");
}
}
优点:符合开闭原则,能对目标对象进行扩展。
缺点:为每一个需要增强的服务都需要创建类,
1.5.2 动态代理
动态代理基本介绍:
1)代理对象不需要实现接口,但是目标对象要实现接口,否则不能使用
2)代理对象的生成,是利用jdk的api,动态的在内存中构建代理对象
3)动态代理也叫作:jdk代理,接口代理
//接口
public interface RentHouse {
public void RentHouse();
}
//真实
public class Landlord implements RentHouse {
@Override
public void RentHouse() {
System.out.println("看房子");
}
}
//代理对象
public class JDKProxy {
private RentHouse rentHouse;
public JDKProxy(RentHouse rentHouse){
this.rentHouse=rentHouse;
}
public Object getProxy(){
//当前对象使用的目标类加载器
ClassLoader classLoader = rentHouse.getClass().getClassLoader();
//目标对象实现的接口类型,使用泛型的方式确认类型
Class<?>[] interfaces = rentHouse.getClass().getInterfaces();
//执行目标对象的方法时,会出发事情处理方法,会把当前执行的对象当做参数传入
InvocationHandler h=((proxy, method, args) -> {
System.out.println("问候客户");
Object invoke = method.invoke(rentHouse);
System.out.println("答谢客户");
return invoke;
});
Object o = Proxy.newProxyInstance(classLoader, interfaces, h);
return o;
}
}
优点:动态代理大大减少了我们的开发任务,同时减少了业务接口依赖。
缺点:始终无法摆脱interface代理的桎梏,无法实现对class的动态代理。
1.5.3 Cglib代理
前提条件:
- 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
- 目标类不能为final
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
/**
* 目标对象,没有实现任何接口
*/
public class Singer{
public void sing() {
System.out.println("唱一首歌");
}
}
/**
* Cglib子类代理工厂
*/
public class ProxyFactory implements MethodInterceptor{
// 维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 给目标对象创建一个代理对象
public Object getProxyInstance(){,
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类---cglib---
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("向观众问好");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("谢谢大家");
return returnValue;
}
}
这里的代码也非常固定,只有部分是需要自己写出
测试
/**
* 测试类
*/
public class Test{
public static void main(String[] args){
//目标对象
Singer target = new Singer();
//代理对象
Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.sing();
}
}
1.6 模板方法模式
1)模板方法模式,在一个抽象类中公开定义了它的方法执行的模板,它的子类可以按需重写方法实现,但调用将以抽象类中定义的方式执行。
2)简单地说,模板方法模式定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重新定义该算法的某些特定步骤。
3)这种类型的设计模式属于行为模式。
//发布预习资料 → 制作课件PPT → 在线直播 → 提交课堂笔记 → 提交源码 → 布置作业 → 检查作业。首先创建AbastractCourse抽象类。
public abstract class AbastractCourse {
public final void createCourse(){
//1.发布预习资料
postPreResoucse();
//2.制作课件PPT
createPPT();
//3.在线直播
liveVideo();
//4.上传课后资料
postResource();
//5.布置作业
postHomework();
if(needCheckHomework()){
checkHomework();
}
}
protected abstract void checkHomework();
//钩子方法
protected boolean needCheckHomework(){return false;}
protected void postHomework(){
System.out.println("布置作业");
}
protected void postResource(){
System.out.println("上传课后资料");
}
protected void liveVideo(){
System.out.println("直播授课");
}
protected void createPPT(){
System.out.println("制作课件");
}
protected void postPreResoucse(){
System.out.println("发布预习资料");
}
}
//设计钩子方法的主要目的是干预执行流程,使得控制行为流程更加灵活,更符合实际业务的需求。钩子方法的返回值一般为适合条件分支语句的返回值(如boolean、int等)。
//创建java课程类
public class JavaCourse extends AbastractCourse {
private boolean needCheckHomework = false;
public void setNeedCheckHomework(boolean needCheckHomework) {
this.needCheckHomework = needCheckHomework;
}
@Override
protected boolean needCheckHomework() {
return this.needCheckHomework;
}
protected void checkHomework() {
System.out.println("检查Java作业");
}
}
//测试
public static void main(String[] args) {
System.out.println("=========架构师课程=========");
JavaCourse java = new JavaCourse();
java.setNeedCheckHomework(false);
java.createCourse();
}