从今天开始学习java常见的二十三种设计模式,每天2种,天晴下雨,雷打不动。
第一天:
工厂类模式和单例类模式
首先来看一下java设计中应当遵循的原则:
1、开闭原则(Open Close Principle)
开闭原则主要意思是 对扩展开放,对修改关闭。当我们需要根据自己的需求实现一些额外的功能时,只能对原始的东西进行扩展,尽量不能对其进行改动,一旦修改,可能就会涉及到功能的失效。而扩展时往往通过继承和复用来实现。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。只有达到这个效果,才算是真正实现了继承基类的效果。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。虽然实现的接口增多了,但是能减少他们之间的耦合度,便于扩展和维护。
5、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。这些设计原则,其实很像软件的设计原则:松耦合,高内聚。软件都是实现了一定功能的,同理,组成软件的各部分代码也都实现了一定的功能,只是我们将其称作模块、基类、接口等等。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
一:工厂模式
工厂模式的主要思想类似于:生活中我们需要某个产品时,我们只需要到对应生产这个产品的工厂去找,不需要我们自己动手去造,虽然可能我们能造这个产品,但不如去工厂取来得便利。java中,实例对应了产品,工厂为我们造实例并返回。如何实现这个实例factory
举例:发送QQ和发送微信消息都为发送消息的行为,他们实现了Send类
interface Send{
void send();
}
class SendWx implements Send{
@Override
void send(){
System.out.print("sendWx");
}
}
class SendQq implements Send{
@Override
void send(){
System.out.print("sendQq");
}
}
创建工厂类
class SendFactory{
public static Send getSendQq(){
return new SendQq();
}
public static Send getSendWx(){
return new SendWx();
}
}
将工厂类中方法申明为静态的,不用实例化工厂类。
以下为调用
class Test{
public static void main(String[] args){
SendFactory factory= new SendFactory();
Send sendQq=factory.getSendQq();
sendQq.send();
}
}
输出为sendQq;
但是当我们想要添加一个新的 发送功能的时候,我们就必须去修改SendFactory,这里违背了开闭原则,因此我们将SendFactory作为实现了一个工厂接口的子类。
工厂类修改如下:
interface Factory{
Send getSend();
}
class QqFactory implements Factory{
Send getSend(){
return new SendQq();
}
class WxFactory implements Factory{
Send getSend(){
return new SendWx();
}
下面为调用
class Test{
public static void main(String[] args){
Factory factory= new QqFactory();
Send sendQq=factory.getSend();
sendQq.send();
}
}
总体上来讲,我们在有大量的类需要创建,并且它们属于同一种类的类时,我们可以使用工厂模式来设计。。
二 、单例类模式
在很多时候,我们的项目中会使用到单例类,他有下面一些优点:
1.只会被创建一次,减少了系统开销
2.有时候系统中一个类只能出现一个,如状态同步类、控制类
实现如下:
大致方法是私有化构造方法,使得外部无法实例化,内部使用静态方法返回该类的实例
单例类的创建实际上分为饱汉式和饿汉式。饿汉式是指一来就创建该类,即下面private static Singleton instance = null;改为private static Singleton instance = new Singleton().此处讲解饱汉式。
public class Singleton {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton instance = null;
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 静态工程方法,创建实例 */
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public Object readResolve() {
return instance;
}
}
可是在多线程中,这个程序有可能同时有几个线程进入,创建是不安全的。我们加入syncronized进行互斥。
public static Singleton getInstance() {
if (instance == null) {
syncheronized(instance){
instance = new Singleton();
}
}
return instance;
}
为什么不把syncronized加在静态方法上呢,因此比起互斥一个方法,互斥一个变量的开销要小。并且,加在方法上时,每次获取实例都需要马上对方法加锁,加在变量上时,我先判断是否已经创建了实例,再进行加锁操作,减少了加锁的频率。
但是,这样的情况,还是有可能有问题的,看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的,分别是为分配空间和初始化实例。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化:
public class Singleton {
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 获取实例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return getInstance();
}
}
在虚拟机中,它能保证一个类在加载时,一定是互斥进行了,因此采用了内部类来实现。内部类在加载时一定是安全的。
单例模式的使用场景:系统中只需要一个类,来储存一些唯一统一的变量;唯一控制类。
学习和写作参考:https://blog.csdn.net/doymm2008/article/details/13288067