26种设计模式
转载:https://zhuanlan.zhihu.com/p/93770973
参考:
https://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html
https://zhuanlan.zhihu.com/p/93770973
https://cloud.tencent.com/developer/article/1602270
设计模式的六大原则**
1、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
5、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
设计模式分类
创建型模式
简单工厂模式
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:
举例如下:(我们举一个发送邮件和短信的例子)
首先,创建二者的共同接口:- public interface Sender {
- public void Send();
- }
复制代码 其次,创建实现类:- public class MailSender implements Sender {
- @Override
- public void Send() {
- System.out.println("this is mailsender!");
- }
- }
- public class SmsSender implements Sender {
-
- @Override
- public void Send() {
- System.out.println("this is sms sender!");
- }
- }
复制代码 最后,建工厂类:- public class SendFactory {
-
- public Sender produce(String type) {
- if ("mail".equals(type)) {
- return new MailSender();
- } else if ("sms".equals(type)) {
- return new SmsSender();
- } else {
- System.out.println("请输入正确的类型!");
- return null;
- }
- }
- }
复制代码 我们来测试下:- public class FactoryTest {
-
- public static void main(String[] args) {
- SendFactory factory = new SendFactory();
- Sender sender = factory.produce("sms");
- sender.Send();
- }
- }
复制代码 输出:this is sms sender!
工厂方法模式
该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:
将上面的代码做下修改,改动下SendFactory类就行,如下:- public Sender produceMail(){
- return new MailSender();
- }
-
- public Sender produceSms(){
- return new SmsSender();
- }
- }
复制代码 测试类如下:- public class FactoryTest {
-
- public static void main(String[] args) {
- SendFactory factory = new SendFactory();
- Sender sender = factory.produceMail();
- sender.Send();
- }
- }
复制代码 输出:this is mailsender!
静态工厂方法模式
将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。- public class SendFactory {
-
- public static Sender produceMail(){
- return new MailSender();
- }
-
- public static Sender produceSms(){
- return new SmsSender();
- }
- }
- public class FactoryTest {
-
- public static void main(String[] args) {
- Sender sender = SendFactory.produceMail();
- sender.Send();
- }
- }
复制代码 输出:this is mailsender!
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后就和代码,就比较容易理解。
请看例子:- public interface Sender {
- public void Send();
- }
复制代码 两个实现类:- public class MailSender implements Sender {
- @Override
- public void Send() {
- System.out.println("this is mailsender!");
- }
- }
- public class SmsSender implements Sender {
-
- @Override
- public void Send() {
- System.out.println("this is sms sender!");
- }
- }
复制代码 两个工厂类:- public class SendMailFactory implements Provider {
-
- @Override
- public Sender produce(){
- return new MailSender();
- }
- }
- public class SendSmsFactory implements Provider{
-
- @Override
- public Sender produce() {
- return new SmsSender();
- }
- }
复制代码 在提供一个接口:- public interface Provider {
- public Sender produce();
- }
复制代码 测试类:- public class Test {
-
- public static void main(String[] args) {
- Provider provider = new SendMailFactory();
- Sender sender = provider.produce();
- sender.Send();
- }
- }
复制代码 其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!
建造者模式
建造模式是对象的创建模式。建造模式可以将一个产品的内部表象(internal representation)与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。
- 需求:用户去汽车店购买汽车。
- 分析:汽车店根据每个用户的需求提取对应汽车
- 建造者超类:Builder
- public abstract class Builder {
- public abstract void setPart(String name, String type);
- public abstract Product getProduct();
- }
复制代码- 建造者对应实现类:ConcreteBuilder
- public class ConcreteBuilder extends Builder {
- private Product product = new Product();
- @Override
- public void setPart(String name, String type) {
- product.setName(name);
- product.setType(type);
- }
- @Override
- public Product getProduct() {
- return product;
- }
- }
复制代码- // 店长
- Director director = new Director();
- // 得到宝马汽车,内部实现提取宝马汽车的详情操作
- Product product = director.getBProduct();
- // 展示汽车信息
- product.showProduct();
复制代码 单例模式
单例模式是设计模式中最常见也最简单的一种设计模式,保证了在程序中只有一个实例存在并且能全局的访问到。比如在Android实际APP 开发中用到的 账号信息对象管理, 数据库对象(SQLiteOpenHelper)等都会用到单例模式。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。下面针对一些例子分析一下我们在开发过程中应用单例模式需要注意的点。
一、作用
单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点
二、适用场景
- 应用中某个实例对象需要频繁的被访问。
- 应用中每次启动只会存在一个实例。如账号系统,数据库系统。
三、常用的使用方式
(1)懒汉式
优点:延迟加载(需要的时候才去加载)
缺点: 线程不安全,在多线程中很容易出现不同步的情况,如在数据库对象进行的频繁读写操作时。
具体实现如下:- public class Singleton {
-
- /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
- private static Singleton instance = null;
-
- /* 私有构造方法,防止被实例化 */
- private Singleton() {
- }
-
- /* 1:懒汉式,静态工程方法,创建实例 */
- public static Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
- }
复制代码 (2)加同步锁
优点:解决了线程不安全的问题。
缺点:效率有点低,每次调用实例都要判断同步锁
注:在Android源码中使用的该单例方法有:InputMethodManager,AccessibilityManager等都是使用这种单例模式。
具体代码如下:- public static synchronized Singleton getInstance() {
- if (instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
复制代码 或- /*加上synchronized,但是每次调用实例时都会加载**/
- public static Singleton getInstance() {
- synchronized (Singleton.class) {
- if (instance == null) {
- instance = new Singleton();
- }
- }
- return instance;
- }
复制代码 (3)双重检验锁
要优化(2)中因为每次调用实例都要判断同步锁的问题,很多人都使用下面的一种双重判断校验的办法。
优点:在并发量不多,安全性不高的情况下或许能很完美运行单例模式
缺点:不同平台编译过程中可能会存在严重安全隐患。
补充:在android图像开源项目Android-Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader)中使用的是这种方式。- /*3.双重锁定:只在第一次初始化的时候加上同步锁*/
- public static Singleton getInstance() {
- if (instance == null) {
- synchronized (Singleton.class) {
- if (instance == null) {
- instance = new Singleton();
- }
- }
- }
- return instance;
- }
复制代码 这种方法貌似很完美的解决了上述效率的问题,它或许在并发量不多,安全性不太高的情况能完美运行,但是,这种方法也有不幸的地方。问题就是出现在这句- instance = new Singleton();
复制代码 在JVM编译的过程中会出现指令重排的优化过程,这就会导致当 instance实际上还没初始化,就可能被分配了内存空间,也就是说会出现 instance !=null 但是又没初始化的情况,这样就会导致返回的 instance 不完整(可以参考:http://www.360doc.com/content/11/0810/12/1542811_139352888.shtml)。
(4)内部类的实现
优点:延迟加载,线程安全(java中class加载时互斥的),也减少了内存消耗。内部类是一种好的实现方式,可以推荐使用一下:- public class SingletonInner {
- private static class SingletonHolder {
- private static SingletonInner instance = new SingletonInner();
- }
-
- /**
- * 私有的构造函数
- */
- private SingletonInner() {
-
- }
-
- public static SingletonInner getInstance() {
- return SingletonHolder.instance;
- }
-
- protected void method() {
- System.out.println("SingletonInner");
- }
- }
复制代码 (5)枚举的方法
这是网上很多人推荐的一种做法,但是貌似使用的不广泛,大家可以试试,具体代码如下:- public enum SingletonEnum {
- /**
- * 1.从Java1.5开始支持;
- * 2.无偿提供序列化机制;
- * 3.绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候;
- */
-
- instance;
-
- private String others;
-
- SingletonEnum() {
-
- }
-
- public void method() {
- System.out.println("SingletonEnum");
- }
-
- public String getOthers() {
- return others;
- }
-
- public void setOthers(String others) {
- this.others = others;
- }
- }
复制代码 通过单例模式的学习告诉我们:
1、单例模式理解起来简单,但是具体实现起来还是有一定的难度。
2、synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。
到这儿,单例模式基本已经讲完了,结尾处,笔者突然想到另一个问题,就是采用类的静态方法,实现单例模式的效果,也是可行的,此处二者有什么不同?
首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!
结构型模式
适配器模式
定义:将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以相互合作。这个定义还好,说适配器的功能就是把一个接口转成另一个接口。
- 以充电器为实例: 手机充电器一般都是5V左右吧,咱天朝的家用交流电压220V,所以手机充电需要一个适配器(降压器)
- 一部手机: Mobile.java
- public class Mobile {
- // 这里传入的是 v5接口,实现了这个接口的类也可以传入
- public void inputPower(V5Power v5Power) {
- int provideV5Power = v5Power.provideV5Power();
- Log.e("---", "手机(客户端): 我需要的是5V电压充电,现在是" + provideV5Power + "V");
- }
- }
复制代码
- 手机依赖一个提供5V电压的接口: V5Power.java
- public interface V5Power {
- public int provideV5Power();
- }
复制代码
- 我们拥有的是220V家用交流电: V220Power.java
- public class V220Power {
- public int provideV220Power() {
- Log.e("---", "现有类: 我们提供的是220v的家用电");
- return 220;
- }
- }
复制代码
- 适配器,完成220V转5V的作用:V5PowerAdapter.java
- public class V5PowerAdapter implements V5Power {
- private int v220power;
- public V5PowerAdapter(V220Power v220Power) {
- v220power = v220Power.provideV220Power();
- }
- @Override
- public int provideV5Power() {
- Log.e("---", "适配器: 经过复杂的操作,将" + v220power + "v电压转为5v");
- return 5;
- }
- }
复制代码- Mobile mobile = new Mobile();
- V5Power v5Power = new V5PowerAdapter(new V200Power());
- mobile.inputPower(v5Power);
复制代码 桥接模式
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
- 主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
以画不同颜色的圆为例,实现共分五步:
- public interface DrawAPI {
- public void drawCircle(int radius, int x, int y);
- }
复制代码
- 2、创建实现了 DrawAPI 接口的实体桥接实现类。RedCircle、GreenCircle
- public class RedCircle implements DrawAPI {
- @Override
- public void drawCircle(int radius, int x, int y) {
- Log.e("---", "Drawing Circle[ color: red, radius: "
- + radius + ", x: " + x + ", y: " + y + "]");
- }
- }
复制代码
- 3、使用 DrawAPI 接口创建抽象类 Shape。
- public abstract class Shape {
- protected DrawAPI drawAPI;
- protected Shape(DrawAPI drawAPI) {
- this.drawAPI = drawAPI;
- }
- public abstract void draw();
- }
复制代码- public class Circle extends Shape {
- private int x, y, radius;
- protected Circle(int x, int y, int radius, DrawAPI drawAPI) {
- super(drawAPI);
- this.x = x;
- this.y = y;
- this.radius = radius;
- }
- @Override
- public void draw() {
- drawAPI.drawCircle(radius, x, y);
- }
- }
复制代码
- 5、使用 Shape 和 DrawAPI 类画出不同颜色的圆。
- // 画红圆
- Circle circle = new Circle(10, 10, 100, new RedCircle());
- circle.draw();
- // 画绿圆
- Circle circle2 = new Circle(20, 20, 100, new GreenCircle());
- circle2.draw();
复制代码 装饰者模式
装饰者模式:若要扩展功能,装饰者提供了比集成更有弹性的替代方案,动态地将责任附加到对象上。
- 先简单描述下装饰者模式发挥作用的地方,当我们设计好了一个类,我们需要给这个类添加一些辅助的功能,并且不希望改变这个类的代码,这时候就是装饰者模式大展雄威的时候了。这里还体现了一个原则:类应该对扩展开放,对修改关闭。
- 需求:设计游戏的装备系统,基本要求,要可以计算出每种装备在镶嵌了各种宝石后的攻击力和描述:
- 1、装备的超类:IEquip.java
- public interface IEquip {
- /**
- * 计算攻击力
- */
- public int caculateAttack();
- /**
- * 装备的描述
- */
- public String description();
- }
复制代码
- 2、各个装备的实现类:
- eg:武器的实现类: ArmEquip.java
- public class ArmEquip implements IEquip {
- @Override
- public int caculateAttack() {
- return 20;
- }
- @Override
- public String description() {
- return "屠龙宝刀";
- }
- }
复制代码
- 3、装饰品的超类(装饰品也属于装备):IEquipDecorator.java
- public interface IEuipDecorator extends IEquip {
- }
复制代码
- 4、装饰品的实现类:
- eg:蓝宝石的实现类(可累加): BlueGemDecorator.java
- public class BlueGemDecorator implements IEuipDecorator {
- private IEquip iEquip;
- public BlueGemDecorator(IEquip iEquip) {
- this.iEquip = iEquip;
- }
- /**
- * 累加攻击力
- */
- @Override
- public int caculateAttack() {
- return 5 + iEquip.caculateAttack();
- }
- @Override
- public String description() {
- return iEquip.description() + "+ 蓝宝石";
- }
- }
复制代码- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.bt_decorator:// 外观模式
- break;
- case R.id.bt_demo1:
- Log.e("---", "一个镶嵌2颗红宝石,1颗蓝宝石的靴子: ");
- IEquip iEquip = new RedGemDecorator(new RedGemDecorator(new BlueGemDecorator(new ShoeEquip())));
- Log.e("---", "攻击力:" + iEquip.caculateAttack());
- Log.e("---", "描述语:" + iEquip.description());
- break;
- case R.id.bt_demo2:
- Log.e("---", "一个镶嵌1颗红宝石,1颗蓝宝石,1颗黄宝石的戒指: ");
- RedGemDecorator redGemDecorator = new RedGemDecorator(new BlueGemDecorator(new YellowGemDecorator(new RingEquip())));
- Log.e("---", "攻击力:" + redGemDecorator.caculateAttack());
- Log.e("---", "描述语:" + redGemDecorator.description());
- break;
- default:
- break;
- }
- }
复制代码 外观模式
定义:提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层的接口,让子系统更容易使用。其实就是为了方便客户的使用,把一群操作,封装成一个方法。
- 需求:我比较喜欢看电影,于是买了投影仪、电脑、音响、设计了房间的灯光、买了爆米花机,然后我想看电影的时候,我需要一键观影和一键关闭。
- 每个设备类的开关等操作:
- eg: 爆米花机:PopcornPopper.java
- public class PopcornPopper {
- public void on() {
- Log.e("PopcornPopper", "---打开爆米花机");
- }
- public void off() {
- Log.e("PopcornPopper", "---关闭爆米花机");
- }
- public void makePopcorn() {
- Log.e("PopcornPopper", "---制作爆米花");
- }
- }
复制代码
- 电影院类:HomeTheaterFacade.java
- public class HomeTheaterFacade {
- private Computer computer;
- private Light light;
- private Player player;
- private PopcornPopper popcornPopper;
- private Projector projector;
- public HomeTheaterFacade(Computer computer, Light light, Player player, PopcornPopper popcornPopper, Projector projector) {
- this.computer = computer;
- this.light = light;
- this.player = player;
- this.popcornPopper = popcornPopper;
- this.projector = projector;
- }
- /**
- * 一键观影
- */
- public void watchMovie() {
- computer.on();
- light.down();
- popcornPopper.on();
- popcornPopper.makePopcorn();
- projector.on();
- projector.open();
- player.on();
- player.make3DListener();
- }
- /**
- * 一键关闭
- */
- public void stopMovie() {
- computer.off();
- light.up();
- player.off();
- popcornPopper.off();
- projector.close();
- projector.off();
- }
- }
复制代码- new HomeTheaterFacade(computer, light, player, popcornPopper, projector).watchMovie();
复制代码 享元模式
主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。
- 主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
以随机获取多种形状为例,共分四步:
- public interface Shape {
- void draw();
- }
复制代码- public class Circle implements Shape {
- private String color;
- private int x;
- private int y;
- private int radius;
- public Circle(String color) {
- this.color = color;
- }
- public void setX(int x) {
- this.x = x;
- }
- public void setY(int y) {
- this.y = y;
- }
- public void setRadius(int radius) {
- this.radius = radius;
- }
- @Override
- public void draw() {
- Log.e("---", "Circle: Draw() [Color : " + color
- + ", x : " + x + ", y :" + y + ", radius :" + radius);
- }
- }
复制代码
- 3、创建一个工厂,生成基于给定信息的实体类的对象。
- public class ShapeFactory {
- private static final HashMap<String, Shape> circleMap = new HashMap<String, Shape>();
- public static Shape getShape(String color) {
- Shape shape = circleMap.get(color);
- if (shape == null) {
- shape = new Circle(color);
- circleMap.put(color, shape);
- Log.e("getShape", "Creating circle of color : " + color);
- }
- return shape;
- }
- }
复制代码
- 4、使用该工厂,通过传递颜色信息来获取实体类的对象。
- for (int i = 0; i < 20; i++) {
- Circle circle = (Circle) ShapeFactory.getShape(getRandomColor());
- circle.setX(getRandomX());
- circle.setY(getRandomY());
- circle.setRadius(100);
- circle.draw();
- }
复制代码 代理模式
一个类代表另一个类的功能。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。可以理解为内存中没有这个对象就创建,有就直接返回这个对象。
- 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
以获取磁盘中的图片为例,总共分三步:
- public interface Image {
- void display();
- }
复制代码
- 2、创建实现接口的实体类 RealImage。对应代理类:ProxyImage。
- public class RealImage implements Image {
- private String fileName;
- public RealImage(String fileName) {
- this.fileName = fileName;
- loadFromDisk(fileName);
- }
- private void loadFromDisk(String fileName) {
- Log.e("RealImage", "loading " + fileName);
- }
- @Override
- public void display() {
- Log.e("RealImage", "Displaying " + fileName);
- }
- }
复制代码- public class ProxyImage implements Image {
- private String fileName;
- private RealImage realImage;
- public ProxyImage(String fileName) {
- this.fileName = fileName;
- }
- @Override
- public void display() {
- if (realImage == null) {
- realImage = new RealImage(fileName);
- }
- realImage.display();
- }
- }
复制代码
- 3、当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。
- Image image = new ProxyImage("test_10mb.png"); // 第一次是new的,图像从磁盘加载 image.display(); // 第二次取缓存,图像不需要从磁盘加载 image.display();
复制代码 组合模式
又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
- 主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
以创建和打印员工的层次结构为例,最小单元示例:
- 1、创建 Employee 类,该类带有 Employee 对象的列表。
- public class Employee {
- private String name;
- // 部门
- private String dept;
- // 工资
- private int salary;
- // 员工 list
- private List<Employee> subordinates;
- public Employee(String name, String dept, int salary) {
- this.name = name;
- this.dept = dept;
- this.salary = salary;
- this.subordinates = new ArrayList<Employee>();
- }
- public void add(Employee e) {
- subordinates.add(e);
- }
- public void remove(Employee e) {
- subordinates.remove(e);
- }
- public List<Employee> getSubordinates() {
- return subordinates;
- }
- @Override
- public String toString() {
- return "Employee{" +
- "name='" + name + '\'' +
- ", dept='" + dept + '\'' +
- ", salary=" + salary +
- ", subordinates=" + subordinates +
- '}';
- }
- }
复制代码
- 2.使用 Employee 类来创建和打印员工的层次结构。
行为型模式
命令模式/事务模式
定义:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。(简化: 将请求封装成对象,将动作请求者和动作执行者解耦。)
- 需求:最近智能家电很火热,假设现在有电视、电脑、电灯等家电,现在需要你做个遥控器控制所有家电的开关,要求做到每个按钮对应的功能供用户个性化,对于新买入家电要有非常强的扩展性。
- 1、家电的API:Door.java
- public class Door {
- public void open() {
- Log.e("Door:", "---打开门");
- }
- public void close() {
- Log.e("Door:", "---关闭门");
- }
- }
复制代码
- 2、把命令封装成类:
- public interface Command {
- public void execute();
- }
复制代码
- 家电实现该接口:DoorOpenCommand.java
- public class DoorOpenCommand implements Command {
- private Door door;
- public DoorOpenCommand(Door door) {
- this.door = door;
- }
- @Override
- public void execute() {
- door.open();
- }
- }
复制代码 - 3、遥控器:ControlPanel.java
- public class ControlPanel {
- private static final int CONTROL_SIZE = 9;
- private Command[] commands;
- public ControlPanel() {
- commands = new Command[CONTROL_SIZE];
- for (int i = 0; i < CONTROL_SIZE; i++) {
- commands[i] = new NoCommand();
- }
- }
- /**
- * 设置每个按钮对应的位置
- */
- public void setCommands(int index, Command command) {
- commands[index] = command;
- }
- /**
- * 模拟点击按钮
- */
- public void keyPressed(int index) {
- commands[index].execute();
- }
- }
复制代码
- 4、定义一个命令,可以干一系列的事情:QuickCommand.java
- QuickCommand quickCloseCommand = new QuickCommand(
- new Command[]{
- new LightOffCommand(light),
- new ComputerOffCommand(computer),
- new DoorCloseCommand(door)
- });
- controlPanel.setCommands(6, quickOpenCommand);
- controlPanel.keyPressed(6);
复制代码
- 5、遥控器面板执行:CommandActivity.java
- controlPanel.setCommands(0, new DoorOpenCommand(door));// 开门
- controlPanel.keyPressed(0);
复制代码 中介者模式
用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。
- 主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
以公共聊天室为例,最小单元示例步骤:
- public class CharRoom {
- public static void showMessage(User user, String message) {
- Log.e("---", new Date().toString()
- + " [" + user.getName() + "] : " + message);
- }
- }
复制代码- public class User {
- private String name;
- public User(String name) {
- this.name = name;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public void sendMessage(String message) {
- CharRoom.showMessage(this, message);
- }
- }
复制代码- User jingbin = new User("jingbin");
- jingbin.sendMessage("Hi~ youlookwhat!");
- //---: Sun Feb 02 08:11:47 GMT+00:00 2020 [jingbin] : Hi~ youlookwhat!
- User jingbin = new User("youlookwhat");
- jingbin.sendMessage("Hi~ jingbin!");
- //---: Sun Feb 02 08:11:49 GMT+00:00 2020 [youlookwhat] : Hi~ jingbin!
复制代码 观察者模式
定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。
- 对于JDK或者Andorid中都有很多地方实现了观察者模式,比如XXXView.addXXXListenter , 当然了 XXXView.setOnXXXListener不一定是观察者模式,因为观察者模式是一种一对多的关系,对于setXXXListener是1对1的关系,应该叫回调。
- 专题接口:Subject.java ;
- public interface Subject {
- /**
- * 注册一个观察者
- */
- public void registerObserver(Observer observer);
- /**
- * 移除一个观察者
- */
- public void removeObserver(Observer observer);
- /**
- * 通知所有观察者
- */
- public void notifyObservers();
- }
复制代码
- 3D服务号的实现类:ObjectFor3D.java
- public class ObjectFor3D implements Subject {
- private ArrayList<Observer> observers = new ArrayList<>();
- /**
- * 3D 彩票的号码
- */
- private String msg;
- @Override
- public void registerObserver(Observer observer) {
- observers.add(observer);
- }
- @Override
- public void removeObserver(Observer observer) {
- int index = observers.indexOf(observer);
- if (index >= 0) {
- observers.remove(index);
- }
- }
- @Override
- public void notifyObservers() {
- for (Observer observer : observers) {
- observer.update(msg);
- }
- }
- /**
- * 主题更新信息
- */
- public void setMsg(String msg) {
- this.msg = msg;
- notifyObservers();
- }
- }
复制代码
- 所有观察者需要实现此接口:Observer.java
- public interface Observer {
- public void update(String msg);
- }
复制代码
- 最后测试:ObserverActivity.java
- public class ObserverActivity extends AppCompatActivity implements View.OnClickListener {
- private ObjectFor3D objectFor3D;
- private ObserverUser1 observerUser1;
- private ObserverUser2 observerUser2;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ActivityObserverBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_observer);
- setTitle("观察者模式");
- binding.tvDefine.setText(EMTagHandler.fromHtml(AppConstant.OBSERVER_DEFINE));
- binding.btMyself.setOnClickListener(this);
- binding.btSystem.setOnClickListener(this);
- }
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.bt_myself:// 自己实现的3D服务号
- // 创建服务号
- objectFor3D = new ObjectFor3D();
- // 创建两个订阅者
- observerUser1 = new ObserverUser1(objectFor3D);
- observerUser2 = new ObserverUser2(objectFor3D);
- // 两个观察者,发送两条信息
- objectFor3D.setMsg("201610121 的3D号为:127");
- objectFor3D.setMsg("20161022 的3D号为:000");
- break;
- case R.id.bt_system://使用Java内置的类实现观察者模式
- loadSystemObserver();
- break;
- default:
- break;
- }
- }
- /**
- * 系统的观察者接口
- */
- private void loadSystemObserver() {
- // 创建2个服务号
- SubjectFor3d subjectFor3d = new SubjectFor3d();//3d
- SubjectForSSQ subjectForSSQ = new SubjectForSSQ();//双色球
- // 创建订阅者
- Observer1 observer1 = new Observer1();
- observer1.registerSubject(subjectFor3d);
- observer1.registerSubject(subjectForSSQ);
- // 发送信息
- subjectFor3d.setMsg("hello 3d'nums : 110 ");
- subjectForSSQ.setMsg("ssq'nums : 12,13,31,5,4,3 15");
- }
- @Override
- protected void onStop() {
- super.onStop();
- if (observerUser1 != null) {
- objectFor3D.removeObserver(observerUser1);
- }
- if (observerUser2 != null) {
- objectFor3D.removeObserver(observerUser2);
- }
- objectFor3D = null;
- }
- }
复制代码 状态模式
定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
- 定义又开始模糊了,理一下,当对象的内部状态改变时,它的行为跟随状态的改变而改变了,看起来好像重新初始化了一个类似的。
- 需求:已自动售货机为例(有已投币、未投币等状态和投币、投币等方法)
- 最初实现待改进的售货机:VendingMachine.java
- public class VendingMachine {
- /**
- * 已投币
- */
- private final static int HAS_MONEY = 0;
- /**
- * 未投币
- */
- private final static int NO_MONEY = 1;
- /**
- * 售出商品
- */
- private final static int SOLD = 2;
- /**
- * 商品售罄
- */
- private final static int SOLD_OUT = 3;
- private int currentStatus = NO_MONEY;
- /**
- * 商品数量
- */
- private int count = 0;
- public VendingMachine(int count) {
- this.count = count;
- if (count > 0) {
- currentStatus = NO_MONEY;
- }
- }
- /**
- * 投入硬币,任何状态用户都可能投币
- */
- public void insertMoney() {
- switch (currentStatus) {
- case NO_MONEY://未投币
- currentStatus = HAS_MONEY;
- Log.e("insertMoney", "---成功投入硬币");
- break;
- case HAS_MONEY://已投币
- Log.e("insertMoney", "---已经有硬币,无需投币");
- break;
- case SOLD://售出商品
- Log.e("insertMoney", "---请稍等...");
- break;
- case SOLD_OUT://商品售罄
- Log.e("insertMoney", "---商品已经售罄, 请勿投币");
- break;
- }
- }
- /**
- * 退币,任何状态用户都可能退币
- */
- public void backMoney() {
- switch (currentStatus) {
- case NO_MONEY://未投币
- Log.e("backMoney", "---您未投入硬币");
- break;
- case HAS_MONEY://已投币
- currentStatus = NO_MONEY;
- Log.e("backMoney", "---退币成功");
- break;
- case SOLD://售出商品
- Log.e("backMoney", "---您已经买了糖果...");
- break;
- case SOLD_OUT://商品售罄
- Log.e("backMoney", "---您未投币,想坑我钱吗?");
- break;
- }
- }
- /**
- * 转动手柄购买,任何状态都有可能转动手柄
- */
- public void turnCrank() {
- switch (currentStatus) {
- case NO_MONEY://未投币
- Log.e("turnCrank", "---请先投入硬币");
- break;
- case HAS_MONEY://已投币
- Log.e("turnCrank", "---正在出商品");
- currentStatus = SOLD;
- dispense();
- break;
- case SOLD://售出商品
- Log.e("turnCrank", "---连续转动也没用...");
- break;
- case SOLD_OUT://商品售罄
- Log.e("turnCrank", "---商品已经售罄");
- break;
- }
- }
- /**
- * 发送商品
- */
- private void dispense() {
- switch (currentStatus) {
- case NO_MONEY://未投币
- case HAS_MONEY://已投币
- case SOLD_OUT://商品售罄
- throw new IllegalStateException("非法的状态...");
- case SOLD://售出商品
- count--;
- Log.e("dispense", "---发出商品...");
- if (count == 0) {
- Log.e("dispense", "---商品售罄");
- currentStatus = SOLD_OUT;
- } else {
- currentStatus = NO_MONEY;
- }
- break;
- }
- }
- }
复制代码
- 改进后的售货机(更具有延展性):VendingMachineBetter.java
- public class VendingMachineBetter {
- private int count = 0;
- private State currentState;
- private State noMoneyState;
- private State hasMoneyState;
- private State soldState;
- private State soldOutState;
- private State winnerState;
- public VendingMachineBetter(int count) {
- noMoneyState = new NoMoneyState(this);
- hasMoneyState = new HasMoneyState(this);
- soldOutState = new SoldOutState(this);
- soldState = new SoldState(this);
- winnerState = new WinnerState(this);
- if (count > 0) {
- this.count = count;
- currentState = noMoneyState;
- }
- }
- /**
- * 放钱
- */
- public void insertMoney() {
- currentState.insertMoney();
- }
- /**
- * 退钱
- */
- public void backMoney() {
- currentState.backMoney();
- }
- /**
- * 转动曲柄
- */
- public void turnCrank() {
- currentState.turnCrank();
- if (currentState == soldState || currentState == winnerState) {
- currentState.dispense();//两种情况会出货
- }
- }
- /**
- * 出商品
- */
- public void dispense() {
- Log.e("VendingMachineBetter", "---发出一件商品");
- if (count > 0) {
- count--;
- }
- }
- public void setState(State state) {
- this.currentState = state;
- }
- public State getNoMoneyState() {
- return noMoneyState;
- }
- public State getHasMoneyState() {
- return hasMoneyState;
- }
- public State getSoldState() {
- return soldState;
- }
- public State getSoldOutState() {
- return soldOutState;
- }
- public State getWinnerState() {
- return winnerState;
- }
- public int getCount() {
- return count;
- }
- }
复制代码- public interface State {
- /**
- * 放钱
- */
- public void insertMoney();
- /**
- * 退钱
- */
- public void backMoney();
- /**
- * 转动曲柄
- */
- public void turnCrank();
- /**
- * 出商品
- */
- public void dispense();
- }
复制代码
- 对应状态的接口实现类:
- eg: 中奖状态:WinnerState.java
- public class WinnerState implements State {
- private VendingMachineBetter machineBetter;
- public WinnerState(VendingMachineBetter machineBetter) {
- this.machineBetter = machineBetter;
- }
- @Override
- public void insertMoney() {
- throw new IllegalStateException("非法操作!");
- }
- @Override
- public void backMoney() {
- throw new IllegalStateException("非法操作!");
- }
- @Override
- public void turnCrank() {
- throw new IllegalStateException("非法操作!");
- }
- @Override
- public void dispense() {
- Log.e("WinnerState", "---您中奖了, 恭喜您,将获得2件商品!");
- machineBetter.dispense();
- if (machineBetter.getCount() == 0) {
- Log.e("WinnerState", "---商品已售罄,中奖失效...");
- machineBetter.setState(machineBetter.getSoldOutState());
- } else {
- machineBetter.dispense();
- if (machineBetter.getCount() > 0) {
- machineBetter.setState(machineBetter.getNoMoneyState());
- } else {
- machineBetter.setState(machineBetter.getSoldOutState());
- }
- }
- }
- }
复制代码- public class SoldState implements State {
- private VendingMachineBetter machineBetter;
- public SoldState(VendingMachineBetter machineBetter) {
- this.machineBetter = machineBetter;
- }
- @Override
- public void insertMoney() {
- Log.e("SoldState", "---正在出货,请勿投币");
- }
- @Override
- public void backMoney() {
- Log.e("SoldState", "---正在出货,没有可退的钱");
- }
- @Override
- public void turnCrank() {
- Log.e("SoldState", "---正在出货,请勿重复摇动手柄");
- }
- @Override
- public void dispense() {
- machineBetter.dispense();
- if (machineBetter.getCount() > 0) {
- machineBetter.setState(machineBetter.getNoMoneyState());
- } else {
- Log.e("SoldState", "---商品已经售罄");
- machineBetter.setState(machineBetter.getSoldOutState());
- }
- }
- }
复制代码- public class StateActivity extends AppCompatActivity implements View.OnClickListener {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ActivityStateBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_state);
- setTitle("状态模式");
- binding.tvDefine.setText(EMTagHandler.fromHtml(AppConstant.STATE_DEFINE));
- binding.btFacadeOld.setText("最初实现待改进");
- binding.btFacadeBetter.setText("改进过的售货机");
- binding.btFacadeOld.setOnClickListener(this);
- binding.btFacadeBetter.setOnClickListener(this);
- }
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.bt_facade_old:// 最初实现待改进
- // 初始化售货机,且里面有3个商品
- VendingMachine vendingMachine = new VendingMachine(3);
- vendingMachine.insertMoney();
- vendingMachine.turnCrank();
- Log.e("测试:", "----------------------");
- vendingMachine.insertMoney();
- vendingMachine.turnCrank();
- Log.e("测试:", "----------------------");
- vendingMachine.insertMoney();
- vendingMachine.turnCrank();
- Log.e("压力测试:", "----------------------");
- vendingMachine.insertMoney();
- vendingMachine.insertMoney();
- vendingMachine.turnCrank();
- vendingMachine.turnCrank();
- vendingMachine.backMoney();
- vendingMachine.turnCrank();
- break;
- case R.id.bt_facade_better:// 改进过的售货机
- VendingMachineBetter machineBetter = new VendingMachineBetter(4);
- // machineBetter.dispense();无法直接操作 出商品(出商品是自动的);
- // 正常: 投币 退币 摇杆
- Log.e("测试:", "----------------------");
- machineBetter.insertMoney();
- machineBetter.turnCrank();
- machineBetter.insertMoney();
- machineBetter.turnCrank();
- machineBetter.insertMoney();
- machineBetter.turnCrank();
- machineBetter.insertMoney();
- machineBetter.turnCrank();
- Log.e("压力测试:", "----------------------");
- machineBetter.insertMoney();
- machineBetter.insertMoney();
- machineBetter.insertMoney();
- machineBetter.backMoney();
- machineBetter.backMoney();
- machineBetter.backMoney();
- machineBetter.turnCrank();
- machineBetter.turnCrank();
- machineBetter.turnCrank();
- break;
- default:
- break;
- }
- }
- }
复制代码 策略模式
策略模式:定义了算法族,分别封装起来,让它们之间可相互替换,此模式让算法的变化独立于使用算法的客户。
- 以创建游戏角色为例子:
- public abstract class Role {
- protected String name;
- // 着装
- protected abstract void display();
- // 逃跑
- protected abstract void run();
- // 攻击
- protected abstract void attack();
- // 防御
- protected abstract void defend();
- }
复制代码- public class RoleA extends Role {
- public RoleA(String name) {
- this.name = name;
- }
- @Override
- protected void display() {
- Log.e("", "样子1");
- }
- @Override
- protected void run() {
- Log.e("", "金蚕脱壳");
- }
- @Override
- protected void attack() {
- Log.e("", "降龙十八掌");
- }
- @Override
- protected void defend() {
- Log.e("", "铁头功");
- }
- }
复制代码- public class RoleB extends Role {
- public RoleB(String name) {
- this.name = name;
- }
- @Override
- protected void display() {
- Log.e("", "样子2");
- }
- @Override
- protected void run() {
- Log.e("", "金蝉脱壳");//拷贝,显得冗余
- }
- @Override
- protected void attack() {
- Log.e("", "降龙十八掌");//拷贝,显得冗余
- }
- @Override
- protected void defend() {
- Log.e("", "铁布衫");
- }
- }
复制代码
- 发现有重复代码后,重构后的父类:Role.java
- /**
- * Created by jingbin on 2016/10/30.
- * 新改进的角色超类:
- * 遵循设计的原则,找出应用中可能需要变化的部分,把它们独立出来,
- * 不要和那些不需要变化的代码混在一起。
- * 我们发现,对于每个角色的display,attack,defend,run都是有可能变化的,于是我们必须把这写独立出来。
- * 再根据另一个设计原则:针对接口(超类型)编程,而不是针对实现编程,于是我们把代码改造成这样:
- */
- public abstract class Role {
- protected String name;
- private IDisplayBehavior iDisplayBehavior;
- private IDefendBehavior iDefendBehavior;
- private IRunBehavior iRunBehavior;
- private IAttackBehavior iAttackBehavior;
- public Role setiDisplayBehavior(IDisplayBehavior iDisplayBehavior) {
- this.iDisplayBehavior = iDisplayBehavior;
- return this;
- }
- public Role setiDefendBehavior(IDefendBehavior iDefendBehavior) {
- this.iDefendBehavior = iDefendBehavior;
- return this;
- }
- public Role setiRunBehavior(IRunBehavior iRunBehavior) {
- this.iRunBehavior = iRunBehavior;
- return this;
- }
- public Role setiAttackBehavior(IAttackBehavior iAttackBehavior) {
- this.iAttackBehavior = iAttackBehavior;
- return this;
- }
- public void display() {
- iDisplayBehavior.display();
- }
- public void defend() {
- iDefendBehavior.defend();
- }
- public void attack() {
- iAttackBehavior.attack();
- }
- public void run() {
- iRunBehavior.run();
- }
- }
复制代码- public interface IDisplayBehavior {
- void display();
- }
复制代码- public class DisplayYZ implements IDisplayBehavior {
- @Override
- public void display() {
- Log.e("---", "样子2");
- }
- }
复制代码- /**
- * 策略模式(Strategy Pattern):定义了算法族,分别封装起来,
- * 让它们之间可相互替换,此模式让算法的变化独立于使用算法的客户。
- */
- public class StrategyActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ActivityStategyBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_stategy);
- setTitle("策略模式");
- binding.tvDefine.setText(EMTagHandler.fromHtml(AppConstant.STRATEGY_DEFINE));
- binding.btStrategyText.setText("创建角色A,并设定样子,攻击,逃跑,防御");
- binding.btStrategyText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- RoleA roleA = new RoleA("---A");
- roleA.setiDisplayBehavior(new DisplayYZ())
- .setiAttackBehavior(new AttackXL())
- .setiDefendBehavior(new DefendTMS())
- .setiRunBehavior(new RunJCTQ());
- roleA.display();// 样子
- roleA.attack();// 攻击
- roleA.run();// 逃跑
- roleA.defend();// 防御
- }
- });
- }
- }
复制代码 - 总结:
- 1、封装变化(把可能变化的代码封装起来)
- 2、多用组合,少用继承(我们使用组合的方式,为客户设置了算法)
- 3、针对接口编程,不针对实现(对于Role类的设计完全的针对角色,和技能的实现没有关系)
- 最后测试:创建角色:
- RoleA roleA = new RoleA("---A");
- roleA.setiDisplayBehavior(new DisplayYZ())
- .setiAttackBehavior(new AttackXL())
- .setiDefendBehavior(new DefendTMS())
- .setiRunBehavior(new RunJCTQ());
- roleA.display();// 样子
- roleA.attack();// 攻击
- roleA.run();// 逃跑
- roleA.defend();// 防御
复制代码 职责链模式
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
- 主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
以Android Studio中打印日志为例,最小单元步骤:
- 1、创建抽象的记录器类 AbstractLogger。
[code]public abstract class AbstractLogger { public static int INFO = 1; public static int DEBUG = 2; public static int ERROR = 3; protected int level; // 责任链中的下一个元素 protected AbstractLogger nextLogger; public void setNextLogger(AbstractLogger nextLogger) { this.nextLogger = nextLogger; } public void logMessage(int level, String message) { if (this.level |