教学内容:
1.创建模式: 单例模式、工厂方法模式(简单工厂+工厂方法模式)、抽象工厂模式、原型模式
2.结构模式:代理模式、装饰器模式、适配器模式
3.行为模式:观察者模式
一、什么是设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。简单说:
模式:在某些场景下,针对某类问题的某种通用的解决方案。
场景:项目所在的环境
问题:约束条件,项目目标等
解决方案:通用、可复用的设计,解决约束达到目标。
二、设计模式的三个分类
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
结构型模式:把类或对象结合在一起形成一个更大的结构。
行为型模式:类和对象如何交互,及划分责任和算法。
如下图所示:
三、各分类中模式的关键点
单例模式:某个类只能有一个实例,提供一个全局的访问点。
简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
原型模式:通过复制现有的实例来创建新的实例。
适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
代理模式:为其他对象提供一个代理以便控制这个对象的访问。
装饰模式:动态的给对象添加新的功能。
观察者模式:对象间的一对多的依赖关系。
四、概说7种设计模式
1.单例模式
1.1立即加载的单例模式--饿汉模式
单例模式演示类
立即加载的单例模式--饿汉模式(最基本的单例模式)--要求会手写
类加载时,会直接实例化单例对象,以后都返回该对象的引用
缺点:类加载时,会直接实例化单例对象,不管我有没有使用到该单例对象,浪费内存
优点:没有加锁,执行效率高,线程安全的实例
public class Singleton {
//当构造方法私有化后,无法在当前类的外部去通过构造方法产生对象
// 创建本类的私有构造方法
private Singleton(){}
private static Singleton singleton = new Singleton();
// 提供一个静态方法 返回当前的对象
public static Singleton getInstance(){
return singleton;
}
}
1.2 延迟加载的单例模式--懒汉模式
延迟加载的单例模式--懒汉模式
不要直接在类加载时实例化,而是在调用方法时,再实例化
优点:不会占用内存
能不能改动代码 使他变得安全呢?怎么该?
public class LazySingleton {
private LazySingleton(){}
private volatile static LazySingleton singleton;
/* 实例 会在调用getInstance方法时创建 线程不安全的
安全 单线程情况下,是安全的,但是在多线程下,
多个线程可能同时执行singleton == null都为true,
会创建多个实例在内存中
*/
public static LazySingleton getInstance(){
if(singleton == null){
singleton = new LazySingleton();
}
return singleton;
}
/* 线程安全的 但是效率低 需要同步,仅在第一次调用初始化时
锁的粒度太大,影响了程序的执行效率*/
public static synchronized LazySingleton getInstance(){
if(singleton == null){
singleton = new LazySingleton();
}
return singleton;
}
/* 双重检验 首先先判断实例是否为null,
为null则使用synchronized锁住类 ,
然后在同步块中,再一次判断实例是否为null,为null则初始化实例
synchronized(需要锁的对象){}
*/
public static LazySingleton getInstance(){
if(singleton == null){// 判断对象是否被初始化
// 对象没有被初始化,则加上类锁
synchronized (LazySingleton.class){
// 同时只有一个线程能够到这里
if(singleton == null){
// 创建实例对象
singleton = new LazySingleton();
}
}
}
// 实例对象的引用
return singleton;
}
}
1.3 静态内部类--创建单例模式
通过静态内部类 完成单例模式的创建
在外部类加载时,并不会加载内部类,也就是不会执行new 实例(),这属于懒加载
只有第一次调用getInstance方法时,才会加载
public class InnerSingleton {
private InnerSingleton(){}
// 静态内部类
private static class Inner{
private static InnerSingleton instance = new InnerSingleton();
}
public static InnerSingleton getInstance(){
return Inner.instance;
}
}
1.4 枚举创建单例模式
public class EnumSingleton {
private EnumSingleton(){}
private static enum SinEnum{
// 自定义的枚举值
TEST;
private EnumSingleton es = new EnumSingleton();
}
public static EnumSingleton getInstance(){
SinEnum s = SinEnum.TEST;
return s.es;
}
}
2.工厂方法模式
A B C --- 具体的产品--- 抽象产品(接口)
根据抽象出产品去创建实际的产品---此时只需要知道产品类型(名称)
假设有两样产品 手机 平板 ----- 都有行为 玩
2.1简单工厂模式
1.定义了一个Product接口,含有方法play
2.提供实现Product接口的实现类
3.定义工厂类(Factory)
4.通过工厂类获取想要的对象,不用再关心具体的细节
代码实现:
接口类:
public interface Product {
public void play();
}
产品类:手机
public class Phone implements Product{
@Override
public void play() {
System.out.println("玩手机");
}
}
产品类:平板
public class IPad implements Product{
@Override
public void play() {
System.out.println("玩平板");
}
}
工厂类:
public class Factory {
public Product getProduct(String type) {
if (type == null){
return null;
}
if (type.equals("Phone")){
return new Phone();
}else if (type.equals("IPad")){
return new IPad();
}
return null;
}
}
用户类:
public class Test {
public static void main(String[] args) {
Factory factory = new Factory();
// 需要一个手机,只需告知给工厂即可,不用管任何的细节,屏蔽了具体的实现
Product phone = factory.getProduct("Phone");
Product ipad = factory.getProduct("IPad");
Product no = factory.getProduct("no");
phone.play();
ipad.play();
System.out.println(no);
}
}
2.2 工厂方法模式
1.定义抽象产品接口
2.根据抽象产品接口,提供实现产品接口的实现类
3.定义抽象工厂接口
4.为不同的具体产品,给出不同的具体工厂类
5.测试(把自己当作顾客)
代码实现:
定义抽象产品接口
public interface Product {
public void play();
}
产品类:手机
public class Phone implements Product{
@Override
public void play() {
System.out.println("玩手机");
}
}
产品类:平板
public class IPad implements Product{
@Override
public void play() {
System.out.println("玩平板");
}
}
定义抽象工厂接口
public interface Factory {
// 方法返回值是我们的抽象产品
public Product create();
}
工厂类:手机
public class PhoneFactory implements Factory{
@Override
public Product create() {
System.out.println("生产手机");
return new Phone();
}
}
工厂类:平板
public class IPadFactory implements Factory{
@Override
public Product create() {
System.out.println("生产平板");
return new IPad();
}
}
用户类:
//顾客
public class Tset {
public static void main(String[] args) {
Factory factory = new PhoneFactory();
Product phone =factory.create();
Factory factory1 =new IPadFactory();
Product ipad =factory1.create();
phone.play();
ipad.play();
}
}
3.抽象工厂模式
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,
而无需指定他们具体的类。-- 创建模式
代入场景:我们作为工厂老板,
工厂目前可以代工ApplePhone、AppleIPad,
拓展业务代工HuawePhone、HuaweiIPad---提供多个类去完成业务
如果我还需要继续扩展业务XiaomiPhone、XiaoMiIPad,需要提供的类就也多
所以为了工厂更好的发展,需要对工厂升级
1.对生产的产品进行细化,然后抽象
抽象产品---手机、平板
2.具体产品类
ApplePhone 和 AppleIPad
HuaweiPhone 和 HuaweiIPad
3.根据不同的品牌给出工厂(需要先对工厂进行抽象)
3.1抽象工厂两个gong--造手机和造平板
3.2 给出具体的Huawei工厂 和 Apple工厂
提升需求 华为 造电脑
代码实现:
产品接口实现:手机
public interface Phone {
public void call();
}
产品接口实现:平板
public interface IPad {
public void study();
}
工厂接口实现:
public interface Factory {
public Phone productPhone();
public IPad productIPad();
}
产品实现:华为手机、华为平板
public class HuaweiIPad implements IPad{
@Override
public void study() {
System.out.println("华为平板多屏互动学习");
}
}
public class HuaweiPhone implements Phone{
@Override
public void call() {
System.out.println("使用华为手机打电话");
}
}
工厂实现:华为工厂
public class HuaweiFactory implements Factory{
@Override
public Phone productPhone() {
System.out.println("生产华为手机");
return new HuaweiPhone();
}
@Override
public IPad productIPad() {
System.out.println("生产华为平板");
return new HuaweiIPad();
}
}
产品实现:苹果手机、苹果平板
public class ApplePhone implements Phone{
@Override
public void call() {
System.out.println("苹果用户给你打电话");
}
}
public class AppleIPad implements IPad{
@Override
public void study() {
System.out.println("使用苹果平板学习");
}
}
工厂实现:苹果工厂
public class AppleFactory implements Factory {
@Override
public Phone productPhone() {
System.out.println("生产苹果手机");
return new ApplePhone();
}
@Override
public IPad productIPad() {
System.out.println("生产苹果平板");
return new AppleIPad();
}
@Override
public Computer productComputer() {
return null;
}
}
用户类:
public class Test {
public static void main(String[] args) {
Factory factory;
Phone phone;
IPad ipad;
factory = new HuaweiFactory();
phone = factory.productPhone();
ipad = factory.productIPad();
phone.call();
ipad.study();
factory = new AppleFactory();
phone = factory.productPhone();
ipad = factory.productIPad();
phone.call();
ipad.study();
}
}
如果我们需要新添加一个华为的电脑售卖的话 只需要在华为工厂接口中添加一个制造电脑方法和添加一个华为电脑类就可以了。
接口:
public interface Factory {
public Phone productPhone();
public IPad productIPad();
public Computer productComputer();
}
华为电脑实现类
public class HuaweiComputer implements Computer {
@Override
public void code() {
System.out.println("用华为的电脑敲代码");
}
}
用户类:
public class Test {
public static void main(String[] args) {
Factory factory;
factory = new HuaweiFactory();
Computer com = factory.productComputer();
com.code();
}
}
4、原型模式
根据一个已经存在的对象,创建一个和他一样的对象。-- 克隆
浅克隆-- 利用Object中clone()实现
1.让被克隆的类实现Cloneable接口
2.重写clone方法,方法访问修饰符public
3.对象.clone()的方式的到一个一样的对象
浅克隆指,被克隆的对象会产生一个新的,但是对象属性不会产生。
深度克隆
1.克隆对象所涉及的自定义类,需要实现序列化接口
2.在需要克隆的类中,添加一个方法,完成序列化反序列化即可。
代码实现:
创建一个车类 使车链接 Serializable接口
public class Car implements Serializable {
}
创建一个人类类
/**
* 人类
* 想要调用Object的clone方法,
* 原型类需要实现Cloneable接口
* 想要实现深度克隆圆形类需要实现Serializable接口
*/
public class Man implements Cloneable, Serializable {
/** 姓名 */
private String name;
/** 人拥有的汽车 */
public Car car = new Car();
// 调用Object的clone方法 浅度克隆
public Man clone() throws CloneNotSupportedException {
Object obj = super.clone();
return (Man) obj;
}
// 深度克隆的实现
public Man depthClone() throws IOException, ClassNotFoundException {
// 获取对象信息,把当前对象写入另一块内存
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(bo);
objOut.writeObject(this);
// 读取内存 创建对象
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream objIn = new ObjectInputStream(bi);
Object obj = objIn.readObject();
return (Man) obj;
}
}
测试:
public class Test {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Man man = new Man();
Man man1 = man.clone();
System.out.println(man == man1);
System.out.println(man.car == man1.car);
// 浅度克隆,可以克隆一杨的对象,但是对象中属性,如果是引用类型,
该属性会指向相同的引用
// 深度克隆,使用对象序列化克隆一样的对象。
// 如果属性为引用类型,不会再重复引用,它会重写创建同类型的数据完成引用
Man man2 = man.depthClone();
System.out.println(man == man2);
System.out.println(man.car == man2.car);
}
}
这里clone会有异常,在这里为了不使代码混乱我们选择抛出异常
System.out的结果为:
false
true 浅度克隆 对象中属性,如果是引用类型,该属性会指向相同的引用
false
false
结构模式
1.代理模式
1.1静态代理
根据目标对象需要代理的行为,抽象出一个接口(包含了需要代理的行为),目标类和代理类都需要去实现该接口,然后将目标对象注入到代理类中,此时就可以在代理类中调用目标对象的行为,并为止附加非功能性逻辑
代码实现:
创建一个目标接口
public interface IEat {
public void eat();
}
创建一个目标类
// 目标类
public class Monitor implements IEat {
@Override
public void eat() {
System.out.println("请大家吃火锅");
}
}
创建第二个目标类:
//目标对象
public class a implements IEat{
@Override
public void eat() {
System.out.println("吃个屁,人都走了");
}
}
创建代理类:
/**
* 代理类
*/
public class LivePeople implements IEat{
// 目标对象
private IEat monitor;
// 通过构造方法传入
public LivePeople(IEat monitor){
this.monitor = monitor;
}
@Override
public void eat() {
System.out.println("订餐");
monitor.eat();
System.out.println("结账,开发票");
}
}
测试类:
public class Test {
public static void main(String[] args) {
IEat a = new LivePeople(new a());
IEat b = new LivePeople(new Monitor());
a.eat();
System.out.println("===================");
b.eat();
}
}
测试结果:
订餐
吃个屁,人都走了
结账,开发票
===================
订餐
请大家吃火锅
结账,开发票
1.2.动态代理之JDK代理
第一步,实现接口InvocationHandler,然后重写invoke方法,在invoke方法中调用目标对的方法
第二步,提供一个自定义的方法,通过Proxy.newProxyInstance()得到代理对象
代码实现:
目标类继续使用上面的目标类
创建代理类
/**
* 代理类
*/
public class LivePeople implements InvocationHandler {
private IEat target;// 目标对象
public LivePeople(IEat target){
this.target = target;
}
// 获取代理对象
public Object getProxy(){
/*
Proxy类中的newProxyInstance()方法 第一个参数:目标对象的类加载器
第二个参数:目标对象实现的所有接口第三个参数:代理类的对象
*/
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("订餐");
Object returnInfo = method.invoke(target,args);// 执行目标对象的方法
System.out.println("结账,开发票");
return returnInfo;
}
}
测试:
public class Test {
public static void main(String[] args) {
LivePeople livePeople = new LivePeople(new Monitor());
IEat ie = (IEat) livePeople.getProxy();
ie.eat();
// 动态代理之JDK代理
}
}
2.装饰器模式
对象功能的扩展能够根据需要来动态地实现。
生活场景:星巴克的分店几乎开遍世界各地。它们提供了各式各样的美味咖啡:爱尔兰咖啡、蓝山咖啡、卡布基诺、雀巢。每样咖啡都有自己的描述属性和收费行为。另外它们还提供各种配料:奶、砂糖、冰块、豆浆。不同的咖啡加入不同的配料计算的价格是不一样的。
1.根据对象抽象一个公共的接口
2.根据接口给出不同的实现类(主料类(主料类产生的对象就是被装饰的对象) 和 配料类--装饰类)
3.在配料类中注入被装饰的对象
4.生产咖啡时,先生产主料(被修饰的对象),然后用配料不断去修饰主料
代码实现:
创建咖啡接口:
/*
材料接口
*/
public interface Stuff {
public String getName();
public int getPrice();
}
创建咖啡对象:
public class AStuff implements Stuff {
@Override
public String getName() {
return "爱尔兰咖啡";
}
@Override
public int getPrice() {
return 10;
}
}
public class BStuff implements Stuff {
@Override
public String getName() {
return "蓝山咖啡";
}
@Override
public int getPrice() {
return 20;
}
}
public class KStuff implements Stuff {
@Override
public String getName() {
return "卡布奇诺";
}
@Override
public int getPrice() {
return 30;
}
}
创建材料类
public class Milk implements Stuff{
private Stuff coffee;
public Milk(Stuff coffee) {
this.coffee = coffee;
}
@Override
public String getName() {
return coffee.getName()+" 加上奶 ";
}
@Override
public int getPrice() {
return coffee.getPrice()+5;
}
}
public class Sugar implements Stuff{
//将装饰对象加入类中
private Stuff coffee;
public Sugar(Stuff coffee) {
this.coffee = coffee;
}
@Override
public String getName() {
return coffee.getName()+" 加上糖 ";
}
@Override
public int getPrice() {
return coffee.getPrice()+2;
}
}
测试:
public class Test {
public static void main(String[] args) {
//需要一个双倍奶双倍糖的爱尔兰咖啡
Stuff aStuff = new AStuff();
aStuff = new Milk(aStuff);//第一份
aStuff = new Milk(aStuff);//第二份奶
aStuff = new Sugar(aStuff);//第一份糖
aStuff = new Sugar(aStuff);//第二份糖
System.out.println(aStuff.getName()+"总价格为:"+aStuff.getPrice());
//需要一个双倍奶双倍糖的卡布奇诺
Stuff kStuff = new KStuff();
kStuff = new Milk(kStuff);//第一份
kStuff = new Milk(kStuff);//第二份奶
kStuff = new Sugar(kStuff);//第一份糖
kStuff = new Sugar(kStuff);//第二份糖
System.out.println(kStuff.getName()+"总价格为:"+kStuff.getPrice());
}
}
3适配器模式
使得原本不兼容的两个接口(功能)可以兼容--搭建了两个接口间的桥梁
假如:我们现在有一台座机,可以通话,有一个照相机,可以进行拍照,能不能有一台设备把两者的功能合一呢?
class Tel{
public void call(){}
}
class Carame{
public void make(){}
}
class Phone extends Tel{
private Carame c = new Carame();
public void make(){
c.make();
}
}
实现适配器的方案,继承或者依赖(推荐使用)
优点:可以让没有任何关联的类,一起运行;
提高了类的复用
灵活性好
缺点:过多的使用适配器,会导致系统非常混乱,不容具体把控
java是单继承,
观察者模式---行为模式
A球队(青年天才) 其他球队都对这个天才垂涎欲滴,都会派出球探观察青年天才的表现,根据它的表现决定球队,应该出多少钱买入该天才。在某一次比赛中,它面对一个传统豪强(强队),世界级的变现(帽子戏法+助攻帽子戏法),6-3大胜
生活场景:多个代理商代理同一个厂家的商品。不同代理商会在厂家出厂价基础上加上自己的利润,成为批发价。当商品的厂价发生变化时,不同代理商品的价格随之变化。
1.主题类(由它产生的对象 就是 被观察的对象) 继承 Observable的类
在主题类,需要针对主题(价格的变化进行关注,设置变化点,然后通知观察者价格有了变化)
public class Product extends Observable {
private double price;
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
// 通知观察者注意到主题的变化
this.setChanged();// 设置变化点
this.notifyObservers(price);//通知观察者
}
}
2.观察者类(由它产生的对象 就是 观察者) 实现Observer接口
重写update(当主题发生变化时,会调用方法)
public class ProductProxy1 implements Observer {
private double price;
/**
* 当主题类的值发生变化后,会调用该方法
* @param o 主题对象
* @param arg 主题更新的值对象
*/
@Override
public void update(Observable o, Object arg) {
double factoryPrice = (double) arg;
this.price = factoryPrice * 1.5;
}
public double getPrice() {
return price;
}
}
3.首先产生主题对象(被观察对象),产生观察者对象,然后给主题对象设置观察者,最后通过更改主题的值,测试观察者是否有观测到主题值的改变
public class Test {
public static void main(String[] args) {
// 产生主题对象 -- 被观察者
Product product = new Product();
// 产生观察者
ProductProxy1 p1 = new ProductProxy1();
// 给主题对象添加观察者
product.addObserver(p1);
// 主题发生变化
product.setPrice(1000);
System.out.println("出厂价格:"+ product.getPrice() +
"\n代理商售卖价格:" + p1.getPrice());
// 主题发生变化
product.setPrice(2000);
System.out.println("出厂价格:"+ product.getPrice() +
"\n代理商售卖价格:" + p1.getPrice());
}
}
学习模式需要在意的点:
1.知道模式大致应用场景
2.模式的名称也是对模式的精炼描述
3.模式它的实现方案
4.使用模式的优缺点