常用设计模式总结
参考:
https://blog.csdn.net/qq_14827935/article/details/78618652
https://www.cnblogs.com/xiaofengwang/p/11255678.html
https://www.cnblogs.com/yueguanguanyun/p/9584501.html
java常用设计模式总结
掌握常用的几种(最起码单例模式、工厂模式),了解其他的设计模式即可,做到手里有粮,心里不慌。首先,掌握每种模式的定义及使用场景。其次,掌握一个形象的例子,简单的过一遍代码。
学习设计模式的真正目的:编程时,有意识地面向接口编程,多用封装、继承、组合、多态等OOP思想,而不仅仅是死记几类设计模式。
常用的设计模式分类:
创建型(创建一个对象):单例模式、工厂模式、抽象工厂模式
结构型(将几个对象组织成一个结构):桥接模式、外观模式、代理模式
行为型(多个对象间的通信):观察者模式、策略模式
其中,工厂模式、桥接模式、策略模式有点像,放在一起理解(几个对象具有共同特征,因此继承共同的接口,然后通过工厂、桥去访问)。另外,工厂模式和外观模式(几个对象间有先后关系,是串行的,而非工厂模式中的并行,因此几个对象组合成一个外观类,通过这个外观类来访问)区别很明显,也因此放在一起理解。
参考引用:
http://www.runoob.com/design-pattern/proxy-pattern.html
https://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
https://www.cnblogs.com/chinajava/p/5880870.html
设计模式定义
被反复使用的,代码设计经验的总结。
设计模式的原则
总结起来,就是多用接口/抽象类,从而增加代码的可扩展性(减少修改代码)。降低模块间的依赖和联系。
体现了OOP的模块化、可扩展性等特征。
工厂模式
定义与使用场合:现在需要创建几个对象,且这几个对象有共同特征,则不需要具体创建各个对象,而是创建对象工厂类即可。
一般常用静态工厂模式。
例子:发送邮件和短信(共同特征:发送的消息)
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 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();
}
}
抽象工厂模式
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则。
定义与使用场景:同上。
例子:同上。
public interface Provider {
public Sender produce();
}
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 class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
总结:如果要新增发送微信,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!
所有工厂模式中,抽象工厂模式最先进。
策略模式及与工厂模式的区别
定义与使用场合:一个系统需要动态地在几种类似的算法中选择一种。
与工厂模式异同:实例化一个对象的位置不同。对工厂模式而言,实例化对象是放在了工厂类里面。而策略模式实例化对象的操作在调用的地方。本质都是继承与多态。
例子: 现有 加/减/乘 几种算法,输入参数返回值都一样(可以理解成类似的算法)。现在需要在调用时动态配置算法策略,实现对不同算法的调用。
public interface Strategy {
public int doOperation(int num1, int num2);
}
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}
public class StrategyPatternDemo {
public static void main(String[] args) {
//实例化对象的位置在调用处
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
单例模式
定义及使用场合:只有一个对象被创建。
例子:
建议采用 饿汉式 创建方法。线程安全,容易实现。初始化慢一点。
public class SingleObject {
//创建 SingleObject 的一个对象
private static SingleObject instance = new SingleObject();
//让构造函数为 private,这样该类就不会被实例化
private SingleObject(){}
//获取唯一可用的对象
public static SingleObject getInstance(){
return instance;
}
}
观察者模式
定义与使用场景:一个对象(subject)被其他多个对象(observer)所依赖。则当一个对象变化时,发出通知,其它依赖该对象的对象都会收到通知,并且随着变化。
比如 声音报警器和闪光灯报警器分别订阅热水器温度,热水器温度过高时,发出通知,两个报警器分别发声、闪光以实现报警。
又比如很多人订阅微信公众号,该公众号有更新文章时,自动通知每个订阅的用户。
**实现:**1,多个观察者要订阅这个对象 2,这个对象要发出通知
例子:
public interface Observer {
public void update();
}
public class Observer1 implements Observer {
@Override
public void update() {
System.out.println("observer1 has received!");
}
}
public class Observer2 implements Observer {
@Override
public void update() {
System.out.println("observer2 has received!");
}
}
public interface Subject {
/*增加观察者*/
public void add(Observer observer);
/*删除观察者*/
public void del(Observer observer);
/*通知所有的观察者*/
public void notifyObservers();
/*自身的操作*/
public void operation();
}
public abstract class AbstractSubject implements Subject {
private Vector<Observer> vector = new Vector<Observer>();
@Override
public void add(Observer observer) {
vector.add(observer);
}
@Override
public void del(Observer observer) {
vector.remove(observer);
}
@Override
public void notifyObservers() {
Enumeration<Observer> enumo = vector.elements();
while(enumo.hasMoreElements()){
enumo.nextElement().update();
}
}
public class MySubject extends AbstractSubject {
@Override
public void operation() {
System.out.println("update self!");
notifyObservers();
}
}
public class ObserverTest {
public static void main(String[] args) {
Subject sub = new MySubject();
sub.add(new Observer1()); //订阅这个对象
sub.add(new Observer2());
sub.operation(); //发出改变的一个通知
}
}
代理模式
定义与使用场景:一个代理类代表一个真实类的功能,通过访问代理类来实现对真实类的访问。
比如买火车票这件小事:黄牛相当于是火车站的代理,我们可以通过黄牛买票,但只能去火车站进行改签和退票。
又比如需要对原有的方法进行修改,就是采用一个代理类调用原有的方法,以避免修改原有代码。
例子:
一个真实对象realSubject提供一个代理对象proxy。通过proxy可以调用realSubject的部分功能*,并添加一些额外的业务处理*,同时可以屏蔽realSubject中未开放的接口。
1、RealSubject 是委托类,Proxy 是代理类;
2、Subject 是委托类和代理类的接口;
3、request() 是委托类和代理类的共同方法;
interface Subject {
void request();
}
class RealSubject implements Subject {
public void request(){
System.out.println("RealSubject");
}
}
class Proxy implements Subject {
private Subject subject;
public Proxy(Subject subject){
this.subject = subject;
}
public void request(){
System.out.println("begin");
subject.request();
System.out.println("end");
}
}
public class ProxyTest {
public static void main(String args[]) {
RealSubject subject = new RealSubject();
Proxy p = new Proxy(subject);
p.request();
}
}
桥接模式及与策略模式的区别
定义与使用场景:访问多种数据库驱动(多个具有共同特征的数据库驱动),不是直接访问,而是通过DriverManager桥来访问。
例子: 不再具体实现了。
与策略模式的区别:(个人觉得较复杂,了解即可。本质都是面向接口编程,体现继承与多态)
策略模式:我要画圆,要实心圆,我可以用solidPen来配置,画虚线圆可以用dashedPen来配置。这是strategy模式。
桥接模式:同样是画圆,我是在windows下来画实心圆,就用windowPen+solidPen来配置,在unix下画实心圆就用unixPen+solidPen来配置。如果要再windows下画虚线圆,就用windowsPen+dashedPen来配置,要在unix下画虚线圆,就用unixPen+dashedPen来配置。
所以相对策略模式,桥接模式要表达的内容要更多,结构也更加复杂。
外观模式
定义与使用场景:见例子。又比如,去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。
例子:计算机启动,需要先启动CPU,再启动memory,最后启动disk。这三个类之间具有先后关系(依赖关系)。
与工厂模式的区别:工程模式多个类具有共同特征(继承一个共同的接口),是并列的。而外观模式多个类是有先后关系,是串行的,用组合。
贴部分代码:
public class Computer {
//是组合,而非继承。这是与工程模式的显著区别。
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
public class User {
public static void main(String[] args) {
Computer computer = new Computer();
//将计算机的启动过程封装成一个类
computer.startup();
computer.shutdown();
}
}
生产者-消费者模式
定义与使用场景:生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
例子:缓冲区一般为队列(FIFO),但在生产消费较为频繁时,队列push,pop内存消耗较大,此时可以考虑环形缓冲区(以数组、链表方式实现)。
通过互斥锁防止缓冲区同时读写。通过信号量控制缓冲区大小(满的时候不允许写,空的时候不允许读)
Java常见的几种设计模式
单例模式
简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例。
getInstance()的返回值是一个对象的引用,并不是一个新的实例,所以不要错误的理解成多个对象。单例模式实现起来也很容易,直接看demo吧
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
按照我的习惯,我恨不得写满注释,怕你们看不懂,但是这个代码实在太简单了,所以我没写任何注释,如果这几行代码你都看不明白的话,那你可以洗洗睡了,等你睡醒了再来看我的博客说不定能看懂。
上面的是最基本的写法,也叫懒汉写法(线程不安全)下面我再公布几种单例模式的写法:
懒汉式写法(线程安全)
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
饿汉式写法
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
静态内部类
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
枚举
public enum Singleton { INSTANCE; public void whateverMethod() { } }
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏。
双重校验锁
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
总结:我个人比较喜欢静态内部类写法和饿汉式写法,其实这两种写法能够应付绝大多数情况了。其他写法也可以选择,主要还是看业务需求吧。
观察者模式
对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式UML图
看不懂图的人端着小板凳到这里来,给你举个栗子:假设有三个人,小美(女,22),小王和小李。小美很漂亮,小王和小李是两个程序猿,时刻关注着小美的一举一动。有一天,小美说了一句:“谁来陪我打游戏啊。”这句话被小王和小李听到了,结果乐坏了,蹭蹭蹭,没一会儿,小王就冲到小美家门口了,在这里,小美是被观察者,小王和小李是观察者,被观察者发出一条信息,然后观察者们进行相应的处理,看代码:
public interface Person { //小王和小李通过这个接口可以接收到小美发过来的消息 void getMessage(String s); }
l
这个接口相当于小王和小李的电话号码,小美发送通知的时候就会拨打getMessage这个电话,拨打电话就是调用接口,看不懂没关系,先往下看
public class LaoWang implements Person { private String name = "小王"; public LaoWang() { } @Override public void getMessage(String s) { System.out.println(name + "接到了小美打过来的电话,电话内容是:" + s); } } public class LaoLi implements Person { private String name = "小李"; public LaoLi() { } @Override public void getMessage(String s) { System.out.println(name + "接到了小美打过来的电话,电话内容是:->" + s); } }
代码很简单,我们再看看小美的代码:
public class XiaoMei { List<Person> list = new ArrayList<Person>(); public XiaoMei(){ } public void addPerson(Person person){ list.add(person); } //遍历list,把自己的通知发送给所有暗恋自己的人 public void notifyPerson() { for(Person person:list){ person.getMessage("你们过来吧,谁先过来谁就能陪我一起玩儿游戏!"); } } }
我们写一个测试类来看一下结果对不对
public class Test { public static void main(String[] args) { XiaoMei xiao_mei = new XiaoMei(); LaoWang lao_wang = new LaoWang(); LaoLi lao_li = new LaoLi(); //小王和小李在小美那里都注册了一下 xiao_mei.addPerson(lao_wang); xiao_mei.addPerson(lao_li); //小美向小王和小李发送通知 xiao_mei.notifyPerson(); } }
完美~
装饰者模式
对已有的业务逻辑进一步的封装,使其增加额外的功能,如Java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。 举个栗子,我想吃三明治,首先我需要一根大大的香肠,我喜欢吃奶油,在香肠上面加一点奶油,再放一点蔬菜,最后再用两片面包夹一下,很丰盛的一顿午饭,营养又健康。(ps:不知道上海哪里有卖好吃的三明治的,求推荐~)那我们应该怎么来写代码呢? 首先,我们需要写一个Food类,让其他所有食物都来继承这个类,看代码:
public class Food { private String food_name; public Food() { } public Food(String food_name) { this.food_name = food_name; } public String make() { return food_name; }; }
代码很简单,我就不解释了,然后我们写几个子类继承它:
//面包类 public class Bread extends Food { private Food basic_food; public Bread(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+面包"; } } //奶油类 public class Cream extends Food { private Food basic_food; public Cream(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+奶油"; } } //蔬菜类 public class Vegetable extends Food { private Food basic_food; public Vegetable(Food basic_food) { this.basic_food = basic_food; } public String make() { return basic_food.make()+"+蔬菜"; } }
这几个类都是差不多的,构造方法传入一个Food类型的参数,然后在make方法中加入一些自己的逻辑,如果你还是看不懂为什么这么写,不急,你看看我的Test类是怎么写的,一看你就明白了
public class Test { public static void main(String[] args) { Food food = new Bread(new Vegetable(new Cream(new Food("香肠")))); System.out.println(food.make()); } }
看到没有,一层一层封装,我们从里往外看:最里面我new了一个香肠,在香肠的外面我包裹了一层奶油,在奶油的外面我又加了一层蔬菜,最外面我放的是面包,是不是很形象,哈哈~ 这个设计模式简直跟现实生活中一摸一样,看懂了吗? 我们看看运行结果吧
运行结果
一个三明治就做好了~
适配器模式
将两种完全不同的事物联系到一起,就像现实生活中的变压器。假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。
public class Test { public static void main(String[] args) { Phone phone = new Phone(); VoltageAdapter adapter = new VoltageAdapter(); phone.setAdapter(adapter); phone.charge(); } } // 手机类 class Phone { public static final int V = 220;// 正常电压220v,是一个常量 private VoltageAdapter adapter; // 充电 public void charge() { adapter.changeVoltage(); } public void setAdapter(VoltageAdapter adapter) { this.adapter = adapter; } } // 变压器 class VoltageAdapter { // 改变电压的功能 public void changeVoltage() { System.out.println("正在充电..."); System.out.println("原始电压:" + Phone.V + "V"); System.out.println("经过变压器转换之后的电压:" + (Phone.V - 200) + "V"); } }
工厂模式
简单工厂模式:一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口
// 抽象产品类 abstract class Car { public void run(); public void stop(); } // 具体实现类 class Benz implements Car { public void run() { System.out.println("Benz开始启动了。。。。。"); } public void stop() { System.out.println("Benz停车了。。。。。"); } } class Ford implements Car { public void run() { System.out.println("Ford开始启动了。。。"); } public void stop() { System.out.println("Ford停车了。。。。"); } } // 工厂类 class Factory { public static Car getCarInstance(String type) { Car c = null; if ("Benz".equals(type)) { c = new Benz(); } if ("Ford".equals(type)) { c = new Ford(); } return c; } } public class Test { public static void main(String[] args) { Car c = Factory.getCarInstance("Benz"); if (c != null) { c.run(); c.stop(); } else { System.out.println("造不了这种汽车。。。"); } } } 工厂方法模式:有四个角色,抽象工厂模式,具体工厂模式,抽象产品模式,具体产品模式。不再是由一个工厂类去实例化具体的产品,而是由抽象工厂的子类去实例化产品 // 抽象产品角色 public interface Moveable { void run(); } // 具体产品角色 public class Plane implements Moveable { @Override public void run() { System.out.println("plane...."); } } public class Broom implements Moveable { @Override public void run() { System.out.println("broom....."); } } // 抽象工厂 public abstract class VehicleFactory { abstract Moveable create(); } // 具体工厂 public class PlaneFactory extends VehicleFactory { public Moveable create() { return new Plane(); } } public class BroomFactory extends VehicleFactory { public Moveable create() { return new Broom(); } } // 测试类 public class Test { public static void main(String[] args) { VehicleFactory factory = new BroomFactory(); Moveable m = factory.create(); m.run(); } }
抽象工厂模式:与工厂方法模式不同的是,工厂方法模式中的工厂只生产单一的产品,而抽象工厂模式中的工厂生产多个产品
/抽象工厂类 public abstract class AbstractFactory { public abstract Vehicle createVehicle(); public abstract Weapon createWeapon(); public abstract Food createFood(); } //具体工厂类,其中Food,Vehicle,Weapon是抽象类, public class DefaultFactory extends AbstractFactory{ @Override public Food createFood() { return new Apple(); } @Override public Vehicle createVehicle() { return new Car(); } @Override public Weapon createWeapon() { return new AK47(); } } //测试类 public class Test { public static void main(String[] args) { AbstractFactory f = new DefaultFactory(); Vehicle v = f.createVehicle(); v.run(); Weapon w = f.createWeapon(); w.shoot(); Food a = f.createFood(); a.printName(); } }
代理模式(proxy)
有两种,静态代理和动态代理。先说静态代理,很多理论性的东西我不讲,我就算讲了,你们也看不懂。什么真实角色,抽象角色,代理角色,委托角色。。。乱七八糟的,我是看不懂。之前学代理模式的时候,去网上翻一下,资料一大堆,打开链接一看,基本上都是给你分析有什么什么角色,理论一大堆,看起来很费劲,不信的话你们可以去看看,我是看不懂他们在说什么。咱不来虚的,直接用生活中的例子说话。(注意:我这里并不是否定理论知识,我只是觉得有时候理论知识晦涩难懂,喜欢挑刺的人一边去,你是来学习知识的,不是来挑刺的)
到了一定的年龄,我们就要结婚,结婚是一件很麻烦的事情,(包括那些被父母催婚的)。有钱的家庭可能会找司仪来主持婚礼,显得热闹,洋气~好了,现在婚庆公司的生意来了,我们只需要给钱,婚庆公司就会帮我们安排一整套结婚的流程。整个流程大概是这样的:家里人催婚->男女双方家庭商定结婚的黄道即日->找一家靠谱的婚庆公司->在约定的时间举行结婚仪式->结婚完毕
婚庆公司打算怎么安排婚礼的节目,在婚礼完毕以后婚庆公司会做什么,我们一概不知。。。别担心,不是黑中介,我们只要把钱给人家,人家会把事情给我们做好。所以,这里的婚庆公司相当于代理角色,现在明白什么是代理角色了吧。
代码实现请看:
//代理接口 public interface ProxyInterface { //需要代理的是结婚这件事,如果还有其他事情需要代理,比如吃饭睡觉上厕所,也可以写 void marry(); //代理吃饭(自己的饭,让别人吃去吧) //void eat(); //代理拉屎,自己的屎,让别人拉去吧 //void shit(); }
文明社会,代理吃饭,代理拉屎什么的我就不写了,有伤社会风化~~~能明白就好
好了,我们看看婚庆公司的代码:
public class WeddingCompany implements ProxyInterface { private ProxyInterface proxyInterface; public WeddingCompany(ProxyInterface proxyInterface) { this.proxyInterface = proxyInterface; } @Override public void marry() { System.out.println("我们是婚庆公司的"); System.out.println("我们在做结婚前的准备工作"); System.out.println("节目彩排..."); System.out.println("礼物购买..."); System.out.println("工作人员分工..."); System.out.println("可以开始结婚了"); proxyInterface.marry(); System.out.println("结婚完毕,我们需要做后续处理,你们可以回家了,其余的事情我们公司来做"); } }
看到没有,婚庆公司需要做的事情很多,我们再看看结婚家庭的代码:
public class NormalHome implements ProxyInterface{ @Override public void marry() { System.out.println("我们结婚啦~"); } }
这个已经很明显了,结婚家庭只需要结婚,而婚庆公司要包揽一切,前前后后的事情都是婚庆公司来做,听说现在婚庆公司很赚钱的,这就是原因,干的活多,能不赚钱吗?
来看看测试类代码:
public class Test { public static void main(String[] args) { ProxyInterface proxyInterface = new WeddingCompany(new NormalHome()); proxyInterface.marry(); } }
运行结果如下:
生产者/消费者模式
什么是生产者/消费者模式?
某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。在生产者与消费者之间在加个缓冲区,我们形象的称之为仓库,生产者负责往仓库了进商品,而消费者负责从仓库里拿商品,这就构成了生产者消费者模式。结构图如下:
生产者消费者模式有如下几个优点:
1、解耦
由于有缓冲区的存在,生产者和消费者之间不直接依赖,耦合度降低。
2、支持并发
由于生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接,生产者只需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区了拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。
3、支持忙闲不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来 了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。
生产者-消费者模型准确说应该是“生产者-仓储-消费者”模型,这样的模型遵循如下的规则:
1、生产者仅仅在仓储未满时候生产,仓满则停止生产。
2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。
3、当消费者发现仓储没产品可消费时候会通知生产者生产。
4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费
此模型将要结合java.lang.Object的wait与notify、notifyAll方法来实现以上的需求。实例代码如下:
创建所谓的“仓库”,此类是(本质上:共同访问的)共享数据区域
//此类是(本质上:共同访问的)共享数据区域 public class SyncStack { private String[] str = new String[10]; private int index; //供生产者调用 public synchronized void push(String sst){ if(index == sst.length()){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.notify();//唤醒在此对象监视器上等待的单个线程 str[index] = sst; index++; } //供消费者调用 public synchronized String pop(){ if(index == 0){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.notify(); index--; String product = str[index]; return product; } //就是定义一个返回值为数组的方法,返回的是一个String[]引用 public String[] pro(){ return str; } }
创建生产者:
public class Producter implements Runnable { private SyncStack stack; public Producter(SyncStack stack){ this.stack = stack; } public void run(){ for(int i = 0;i<stack.pro().length;i++){ String producter = "产品" + i; stack.push(producter); System.out.println("生产了:" + producter); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
创建消费者:
public class Consumer implements Runnable { private SyncStack stack; public Consumer(SyncStack stack){ this.stack = stack; } public void run(){ for(int i=0;i<stack.pro().length;i++){ String consumer = stack.pop(); System.out.println("消费了:" + consumer ); try { Thread.sleep(400); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
测试类:
public class TestDeam { public static void main(String[] args) { SyncStack stack = new SyncStack(); Consumer p = new Consumer(stack); Producter c = new Producter(stack); new Thread(p).start(); new Thread(c).start(); } }
测试结果:
生产了:产品0 消费了:产品0 生产了:产品1 生产了:产品2 消费了:产品2 生产了:产品3 消费了:产品3 生产了:产品4 生产了:产品5 生产了:产品6 消费了:产品5 生产了:产品7 消费了:产品6 消费了:产品7 生产了:产品8 生产了:产品9 消费了:产品8 消费了:产品9 消费了:产品4 消费了:产品1
十种常用的设计模式
最近发现一个网站对设计模式讲解的非常有深度 点这里 设计模式
1. 单例模式:
实现方式:
a) 将被实现的类的构造方法设计成private的。
b) 添加此类引用的静态成员变量,并为其实例化。
c) 在被实现的类中提供公共的CreateInstance函数,返回实例化的此类,就是b中的静态成员变量。
应用场景:
优点:
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
3.提供了对唯一实例的受控访问。
4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
5.允许可变数目的实例。
6.避免对共享资源的多重占用。
缺点:
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
使用注意事项:
1.使用时不能用反射模式创建单例,否则会实例化一个新的对象
2.使用懒单例模式时注意线程安全问题
3.单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
适用场景:
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3.有状态的工具类对象。
4.频繁访问数据库或文件的对象。
以下都是单例模式的经典使用场景:
1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
2.控制资源的情况下,方便资源之间的互相通信。如线程池等。
应用场景举例:
1.外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件
2. Windows的TaskManager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
10. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
2. 策略模式:
实现方式:
a) 提供公共接口或抽象类,定义需要使用的策略方法。(策略抽象类)
b) 多个实现的策略抽象类的实现类。(策略实现类)
c) 环境类,对多个实现类的封装,提供接口类型的成员量,可以在客户端中切换。
d) 客户端 调用环境类 进行不同策略的切换。
注:Jdk中的TreeSet和 TreeMap的排序功能就是使用了策略模式。
策略模式的优点
(1)策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
(2)使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
策略模式的缺点
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
(2)由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
3. 代理模式:
一)静态代理
实现方式:
a) 为真实类和代理类提供的公共接口或抽象类。(租房)
b) 真实类,具体实现逻辑,实现或继承a。(房主向外租房)
c) 代理类,实现或继承a,有对b的引用,调用真实类的具体实现。(中介)
d) 客户端,调用代理类实现对真实类的调用。(租客租房)
二)动态代理
实现方式:
a) 公共的接口(必须是接口,因为Proxy类的newproxyinstance方法的第二参数必须是个接口类型的Class)
b) 多个真实类,具体实现的业务逻辑。
c) 代理类,实现InvocationHandler接口,提供Object成员变量,和Set方法,便于客户端切换。
d) 客户端,获得代理类的实例,为object实例赋值,调用Proxy.newproxyinstance方法在程序运行时生成继承公共接口的实例,调用相应方法,此时方法的执行由代理类实现的Invoke方法接管。
jdk动态代理使用的局限性:
通过反射类Proxy
和InvocationHandler
回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理。
4. 观察者模式:
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
实现方式:
a) 角色抽象类(提供对观察者的添加,删除和通知功能)。
b) 角色具体类,实现a,维护一个c的集合(对角色抽象类的实现)。
c) 观察者抽象类(被角色通知后实现的方法)。
d) 观察者实现类,实现c(多个)。
注:JDK提供了对观察者模式的支持,使用Observable类和Observer接口
两种模型(推模型和拉模型):
■ 推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
■ 推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
5. 装饰模式:
实现方式:
a) 抽象的被装饰角色 (所有的角色都要直接或间接的实现本角色)
b) 具体的被装饰角色,实现或继承a (被功能扩展的角色)
c) 装饰角色,实现或继承a (本类有对a的引用,所有的具体装饰角色都需要继承这个角色)
d) 多个具体修饰角色 ,继承c(对被装饰角色的功能扩展,可以任意搭配使用)
意图:
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。该模式以对客 户端透明的方式扩展对象的功能。
适用环境:
(1)在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
(2)处理那些可以撤消的职责。
(3)当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的 子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
6. 适配器模式:
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
1. 类适配器(子类继承方式)
实现方式:
a) 目标抽象角色(定义客户要用的接口)
b) 适配器(实现a继承c,作为一个转换器被客户调用)
c) 待适配器(真正需要被调用的)
d) 客户端(借用a的实例调用c的方法)
2. 对象适配器(对象的组合方式)
实现方式:
a) 目标抽象角色(定义客户要用的接口)
b) 适配器(实现a,维护一个c的引用,作为一个转换器被d调用)
c) 待适配器(真正需要被调用的)
d) 客户端(此类,借用a类的实例调用c类的方法,类似静态代理,但是解决的问题不同)
3. 缺省的方式
实现方式:
a) 抽象接口
b) 实现a的适配器类(空实现)
c) 客户端,继承b,调用b中的方法,不必直接实现a(直接实现a需要实现a中的所有的方法)
适配器模式的优点:
1. 更好的复用性
系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
2. 更好的扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
适配器模式的缺点:
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
7. 命令模式
将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或记录日志,以及支持可撤销的操作
将“发出请求的对象”和”接收与执行这些请求的对象”分隔开来。
实现方式:
a) 抽象的命令角色 , 如:菜单(规定可以点哪些菜)
b) 具体的命令角色(实现a 维护一个对c的引用),如:订单(已点的菜)
c) 接收者(具体执行命令的角色),实际操作时,很常见使用"聪明"命令对象,也就是直接实现了请求,而不是将工作委托给c (弊端?) 如:厨师接收订单后做菜
d) 调用者(维护一个对a的引用),如:服务员负责点菜并把订单推给厨师
e) 客户端 调用d发出命令进而执行c的方法,如:顾客点餐
效果:
1)、command模式将调用操作的对象和实现该操作的对象解耦
2)、可以将多个命令装配成一个复合命令,复合命令是Composite模式的一个实例
3)、增加新的command很容易,无需改变已有的类
适用性:
1)、抽象出待执行的动作以参数化某对象
2)、在不同的时刻指定、排列和执行请求。如请求队列
3)、支持取消操作
4)、支持修改日志
5)、用构建在原语操作上的高层操作构造一个系统。支持事物
8. 组合模式
将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和复杂对象的使用具有一致性。
实现方式:
a) 抽象的构件接口 (规范执行的方法),b及c都需实现此接口,如:Junit中的Test接口
b) 叶部件(实现a,最小的执行单位),如:Junit中我们所编写的测试用例
c) 组合类(实现a并维护一个a的集合[多个b的组合]),如:Junit中的 TestSuite
d) 客户端 可以随意的将b和c进行组合,进行调用
什么情况下使用组合模式:
当发现需求中是体现部分与整体层次结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了。
9. 简单工厂模式
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
实现方式:
a) 抽象产品类(也可以是接口)
b) 多个具体的产品类
c) 工厂类(包括创建a的实例的方法)
优点:
工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。
缺点:
由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;
10. 模板方法模式
实现方式:
a) 父类模板类(规定要执行的方法和顺序,只关心方法的定义及顺序,不关心方法实现)
b) 子类实现类(实现a规定要执行的方法,只关心方法实现,不关心调用顺序)
优点:
1)封装不变部分,扩展可变部分:把认为不变部分的算法封装到父类实现,可变部分则可以通过继承来实现,很容易扩展。
2)提取公共部分代码,便于维护。
3)行为由父类控制,由子类实现。
缺点:
模板方法模式颠倒了我们平常的设计习惯:抽象类负责声明最抽象、最一般的事物属性和方法,实现类实现具体的事物属性和方法。在复杂的项目中可能会带来代码阅读的难度。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用