1.单例设计模式
所谓单例设计模式简单说就是无论程序如何运行,采用单例设计模式的类(Singleton类)永远只会有一个实例化对象产生。具体实现步骤如下:
(1) 将采用单例设计模式的类的构造方法私有化(采用private修饰)。
(2) 在其内部产生该类的实例化对象,并将其封装成private static类型。
(3) 定义一个静态方法返回该类的实例。
示例代码如下:
class Singleton {
private static Singleton instance = new Singleton();// 在内部产生本类的实例化对象
public static Singleton getInstance() { // 通过静态方法返回instance对象
return instance;
}
private Singleton() { // 将构造方法封装为私有化
}
public void print() {
System.out.println("Hello World!!!");
}
}
public class SingletonDemo {
public static void main(String args[]) {
Singleton s1 = null; // 声明对象
Singleton s2 = null; // 声明对象
Singleton s3 = null; // 声明对象
s1 = Singleton.getInstance(); // 取得实例化对象
s2 = Singleton.getInstance(); // 取得实例化对象
s3 = Singleton.getInstance(); // 取得实例化对象
s1.print(); // 调用方法
s2.print(); // 调用方法
s3.print(); // 调用方法
}
}
单例模式实现方式;
/**
*
* 单例模式的实现:饿汉式,线程安全 但效率比较低
*/
public class SingletonTest {
private SingletonTest() {
}
private static final SingletonTest instance = new SingletonTest();
public static SingletonTest getInstancei() {
return instance;
}
}
/**
* 单例模式的实现:饱汉式,非线程安全
*
*/
public class SingletonTest {
private SingletonTest() {
}
private static SingletonTest instance;
public static SingletonTest getInstance() {
if (instance == null)
instance = new SingletonTest();
return instance;
}
}
/**
* 线程安全,但是效率非常低
* @author vanceinfo
*
*/
public class SingletonTest {
private SingletonTest() {
}
private static SingletonTest instance;
public static synchronized SingletonTest getInstance() {
if (instance == null)
instance = new SingletonTest();
return instance;
}
}
最后一种推荐:
/**
* 线程安全 并且效率高
*
*/
public class SingletonTest {
private static SingletonTest instance;
private SingletonTest() {
}
public static SingletonTest getIstance() {
if (instance == null) {
synchronized (SingletonTest.class) {
if (instance == null) {
instance = new SingletonTest();
}
}
}
return instance;
}
}
但是个人还是最喜欢第一种,简单,好理解,线程安全。
单例模式的好处:
1、节省不必要的内存开销,屏蔽对象创建的复杂性。
2、因为不需要重新创建对象,因此访问速度快。
3、容易驾驭产生的对象,因为只有一个。在不使用单例的情况下,往往会产生很多对象,这些对象的处理是通过系统的守护线程垃圾收集器去处理,自己不需要去主动清理,但是你并不知道垃圾清理的效果怎么样,对于有些无用的对象可能对系统产生的影响是我们驾驭不了的,当由于垃圾对象导致系统出现问题的bug可能非常难解决。
单例的使用:
1、spring框架产生对象默认就是单例的。
2、读取配置文件的类可以设置为单例。
3、数据库连接可以设置为单例,但是不是很好,因为连接会过期,但是对象可能还存在,因此单例获取的对象可能已经连接失效。
4、项目里面的service,dao类的对象其实只需要一个就行了,这里可以使用单例的方式获取service和dao。
2.工厂设计模式
程序在接口和子类之间加入了一个过渡端,通过此过渡端可以动态取得实现了共同接口的子类实例化对象。
示例代码如下:
interface Animal { // 定义一个动物的接口
public void say(); // 说话方法
}
class Cat implements Animal { // 定义子类Cat
@Override
public void say() { // 覆写say()方法
System.out.println("我是猫咪,喵呜!");
}
}
class Dog implements Animal { // 定义子类Dog
@Override
public void say() { // 覆写say()方法
System.out.println("我是小狗,汪汪!");
}
}
class Factory { // 定义工厂类
public static Animal getInstance(String className) {
Animal a = null; // 定义接口对象
if ("Cat".equals(className)) { // 判断是哪个子类的标记
a = new Cat(); // 通过Cat子类实例化接口
}
if ("Dog".equals(className)) { // 判断是哪个子类的标记
a = new Dog(); // 通过Dog子类实例化接口
}
return a;
}
}
public class FactoryDemo {
public static void main(String[] args) {
Animal a = null; // 定义接口对象
a = Factory.getInstance(args[0]); // 通过工厂获取实例
if (a != null) { // 判断对象是否为空
a.say(); // 调用方法
}
}
}
工厂模式的好处:
如果有许多地方都需要生成A的对象,那么你需要写很多A a=new A()。
如果需要修改的话,你要修改许多地方。
但是如果用工厂模式,你只需要修改工厂代码。其他地方引用工厂,可以做到只修改一个地方,其他代码都不动,就是解耦了。
3.适配器模式
如果一个类要实现一个具有很多抽象方法的接口,但是本身只需要实现接口中的部分方法便可以达成目的,所以此时就需要一个中间的过渡类,但此过渡类又不希望直接使用,所以将此类定义为抽象类最为合适,再让以后的子类直接继承该抽象类便可选择性的覆写所需要的方法,而此抽象类便是适配器类。
示例代码如下:
interface Window {// 定义Window窗口接口,表示窗口操作
public void open();// 窗口打开
public void close();// 窗口关闭
public void iconified();// 窗口最小化
public void deiconified();// 窗口恢复
public void activated();// 窗口活动
}
// 定义抽象类实现接口,在此类中覆写方法,但是所有的方法体为空
abstract class WindowAdapter implements Window {
public void open() {
};// 窗口打开
public void close() {
};// 窗口关闭
public void iconified() {
};// 窗口最小化
public void deiconified() {
};// 窗口恢复
public void activated() {
};// 窗口活动
}
// 子类继承WindowAdapter抽象类,选择性实现需要的方法
class WindowImpl extends WindowAdapter {
public void open() {
System.out.println("窗口打开");// 实现open()方法
}
public void close() {
System.out.println("窗口关闭");// 实现close()方法
}
}
public class AdapterDemo {
public static void main(String args[]) {
Window win = new WindowImpl(); // 实现接口对象
// 调用方法
win.open();
win.close();
}
}
4.生产者--消费者模式
4.1、实例讲解
这种模式我现在经常用(即ActiveMQ):
生产者:就是生产数据的,如我经常往队列里面塞数据,我就是生产者。
消费者:从队列里面取数据并处理,这就是消费者。
还有一个使用的例子就是线程池:Executor接口
4.2.分析
以上的队列就是个缓冲区。
为了不至于太抽象,我们举一个寄信的例子(虽说这年头寄信已经不时兴,但这个例子还是比较贴切的)。假设你要寄一封平信,大致过程如下:
1、你把信写好——相当于生产者制造数据
2、你把信放入邮筒——相当于生产者把数据放入缓冲区
3、邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区
4、邮递员把信拿去邮局做相应的处理——相当于消费者处理数据
★优点
可能有同学会问了:这个缓冲区有什么用捏?为什么不让生产者直接调用消费者的某个函数,直接把数据传递过去?搞出这么一个缓冲区作甚?
其实这里面是大有讲究的,大概有如下一些好处。
◇解耦
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
接着上述的例子,如果不使用邮筒(也就是缓冲区),你必须得把信直接交给邮递员。有同学会说,直接给邮递员不是挺简单的嘛?其实不简单,你必须得认识谁是邮递员,才能把信给他(光凭身上穿的制服,万一有人假冒,就惨了)。这就产生和你和邮递员之间的依赖(相当于生产者和消费者的强耦合)。万一哪天邮递员换人了,你还要重新认识一下(相当于消费者变化导致修改生产者代码)。而邮筒相对来说比较固定,你依赖它的成本就比较低(相当于和缓冲区之间的弱耦合)。
◇支持并发(concurrency)
生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。
使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种,后面的帖子会讲两种并发类型下的应用)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。
其实当初这个模式,主要就是用来处理并发问题的。
从寄信的例子来看。如果没有邮筒,你得拿着信傻站在路口等邮递员过来收(相当于生产者阻塞);又或者邮递员得挨家挨户问,谁要寄信(相当于消费者轮询)。不管是哪种方法,都挺土的。
◇支持忙闲不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
为了充分复用,我们再拿寄信的例子来说事。假设邮递员一次只能带走1000封信。万一某次碰上情人节(也可能是圣诞节)送贺卡,需要寄出去的信超过1000封,这时候邮筒这个缓冲区就派上用场了。邮递员把来不及带走的信暂存在邮筒中,等下次过来时再拿走。
费了这么多口水,希望原先不太了解生产者/消费者模式的同学能够明白它是怎么一回事。然后在下一个帖子中,我们来说说如何确定数据单元。
另外,为了方便阅读,把本系列帖子的目录整理如下:
1、如何确定数据单元
2、队列缓冲区
3、队列缓冲区
4、双缓冲区
5.策略模式
策略模式 定义了算法族,并分别封装起来,让它们之间可以相互替换。此模式让算法的变化独立于使用算法的客户。
实际应用:魂斗罗的游戏大家应该都不陌生。在游戏中能够吃很多道具,来更换子弹的类型,有炫弹,散弹,直弹,火箭弹等等。这个我们就可以设计成策略模式。
分析:策略模式主要是把主体类的行为(动作)(在魂斗罗中指的是把使用各种类型子弹的行为)分离出来,独立封装成类(接口或者说是抽象类)(在魂斗罗中是指可以发射子弹)。某一类行为具有不同的实现方法(即可以发射棉花弹,散弹,直弹,火箭弹),这就组成了一组行为(就是定义中所讲到的算法族),这些行为之间是可以相互替代的(吃了不同的道具,就可以更换成不同类型的子弹)。
此模式让算法的变化独立于使用算法的客户:即在更换各种类型的子弹的时候,人这个主体并未发生改变。
利用到的设计原则:封装变化,针对接口编程,而非具体实现编程。
如果现在我再增加一个能够发射前后散弹的子弹类型,那么并不会改变类robots的任何代码,只需要增加一个类并继承shot即可。即松耦合原则。
6.代理模式