面向对象设计原则与设计模式_第1页
面向对象设计原则与设计模式_第2页
面向对象设计原则与设计模式_第3页
面向对象设计原则与设计模式_第4页
面向对象设计原则与设计模式_第5页
已阅读5页,还剩87页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

面向对象设计原则&设计模式

•面向对象设计原则

•开闭原则(OpenClosePrinciple)

对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔

的效果。

•作用

・1.对软件测试的影响

软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的

测试代码仍然能够正常运行。

・2.可以提高代码的可复用性

粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以

提高代码的可复用性。

・3.可以提高软件的可维护性

遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。

•实现方式

通过"抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相

对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。

•里氏代换原则(LiskovSubstitutionPrinciple)

子类可以扩展父类的功能,但不能改变父类原有的功能。一个软件实体如果使用的是一个父类的话,

那么一定适用于其子类,而且它察觉不出父类对象和子类对象之间的区别。也就是说,把父类都替

换成它的子类,程序的行为没有发生变化。

•定义总结

・子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

・子类中可以增加自己特有的方法。

・当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法

更宽松。

如果子类重载父类的方法时,方法的参数比父类方法更严格,此时不能保证子类在什么地

方都可以替换父类。

・当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法

的的输出/返回值)要比父类的方法更严格或相等。

如果子类返回比父类宽松,也不能保证在什么地方都可以替换父类。例如:父类返回鸟

(会飞),子类返回动物(不是所有动物都会飞).

•作用

・里氏替换原则是实现开闭原则的重要方式之一。

・它克服了继承中重写父类造成的可复用性变差的缺点.

・它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的

可能性。

•加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,

降低需求变更时引入的风险。

•依赖倒转原则(DependenceInversionPrinciple)

核心思想:要面向接口编程,不要面向实现编程。目的:通过要面向接口的编程来降低类间的耦

合性。在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构

要比以细节为基础搭建起来的架构要稳定得多。抽象指的是接口或者抽象类,而细节是指具体的实

现类。

•作用

・降低类间的耦合性。

•提高系统的稳定性。

遵循开闭原则,是实现开闭原则的重要途径之一。

・减少并行开发引起的风险。

如果依赖实现类,由于实现类是会变更的,风险高。

・提高代码的可读性和可维护性。

•实现原则的4点要求

•每个类尽量提供接口或抽象类,或者两者都具备。

・变量的声明类型尽量是接口或者是抽象类。

・任何类都不应该从具体类派生。

・使用继承时尽量遵循里氏替换原则。

•举例说明:

•顾客购物程序

分析科辨反映了”1•客美•与”商店类.的关系.药店类中有sellQ方法,瞬客类通过iS方法由物以下代码定义了附落类通过曲关网店

ShaoguanShop的物:

OLclassCustoaer1

02.publicvoidshopping〈ShaoguanShopshopIi

01,川粕

04Syatva.out.printInlihop.a«ll0):

05.)

06.1

但JI,这斡设计存在缺点,如果该做客也从第夕1家商店(如要理网店WuyuanShop)阵物.就要将幽客的代6修改如下

0Lcl&sxCuitaner(

01publiciruidshopping(VuyuanShopshop){

01/硒

04.Svstea.out.printin(shop.s«110);

01)

0&}

顾客15更换一冢酒店,都要修改一次代码,这明罪违背了开闭朦®1.存在以上缺点的蛔是:颐客类及计时间具体的商店类御定了,这违雷了

依硒■原则.M决方法且定义11要身网店一和窗关网店”的共同接□Shop.Sfi客类面向该按口给程,其代玛修改如下

0LciassCustcner{

OXpublicvoiddnppini(Shopshop){

01M狗

04.Systea.out.printIn(shop.s«110):

05.)

・顾客购物程序类图

•单一职责原则(SingleResponsibilityPrinciple)

一个类应该有且只有一个变化的原因。所谓职责是指类变化的原因。如果一个类有多于一个的动机

被改变,那么这个类就具有多于一个的职责。核心就是控制类的粒度大小、将对象解耦、提高其内

聚性。单一职责原则适用于类、接口、方法。

・优点

•降低类的复杂度。

一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。

•提高类的可读性。

复杂性降低,自然其可读性会提高。

•提高系统的可维护性。

可读性提高,那自然更容易维护了.

・变更引起的风险降低。

变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功

能的影响。

•高内聚,彳氐耦合

・高内聚:内聚是指类内部的属性和行为,高内聚就是指:一个类的属性和行为与这个类非

常密切,称为高内聚。

・低耦合:耦合是指类与类之间或者模块与模块之间的联系,低耦合就是指:耦合度低,易

重用、使用灵活。

•接口隔离原则(InterfaceSegregationPrinciple)

客户端不应该被迫依赖于它不使用的方法。一个类对另一个类的依赖应该建立在最小的接口上。

以上两个定义的含义是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接

口供所有依赖它的类去调用。

・与单一职责原则的区别

都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的。

・单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。

・单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接

□,主要针对抽象和程序整体框架的构建。

•优点

•提高系统的灵活性和可维护性。

将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散。

・接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。

・如果接口的粒度大小定义合理,能够保证系统的稳定性。

如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无

法提供定制服务,给整体项目带来无法预料的风险。

・使用多个专门的接口还能够体现对象的层次。

因为可以通过接口的继承,实现对总接口的定义

・能减少项目工程中的代码冗余。

过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代

码。

•举例说明

・学生成绩管理程序

学生成绩管理程序一般包含插入成绩、删除成绩、修改成绩、计算总分、计算均分、打印

成绩信息、查询成绩信息等功能,如果将这些功能全部放到一个接口中显然不太合理,正

确的做法是将它们分别放在输入模块、统计模块和打印模块等3个模块中。

・学生成绩管理程序的类图

ooo

InputModuleCountModulePrintModule

+insert():void+countTotalScore():void+printstulnfb():void

+delete():void+countAverage():void+queryStuInfb():void

+modifyV():void

实现类

StuScoreList

-StuScoreList()

+getlnpulModule():InputModule

+getCountModule():CountModule

+getPrintModule():PrintModule

+insert():void

+delete():void

+modify():void

+countTotalScore():void

+countAverage():void

+printStuInfd():void

+queryStulnfo():void

•迪米特法则(最少知识原则)(DemeterPrinciple)

定义是:只与你的直接朋友交谈,不跟“陌生人"说话。其含义是:如果两个软件实体无须直接通

信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合

度,提高模块的相对独立性。

•优点

•降低了类之间的耦合度,提高了模块的相对独立性。

・由于亲合度降低,从而提高了类的可复用率和系统的扩展性。

•总结

・从依赖者的角度来说,只依赖应该依赖的对象。

・从被依赖者的角度说,只暴露应该暴露的方法。

・举例说明

・明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与

媒体公司的业务洽淡等.这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以

适合使用迪米特法则。

・明星与经纪人关系类图

•合成复用原则(CompositeReusePrinciple)

要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实

现.

•复用分类

・继承复用

继承复用虽然有简单和易实现的优点,但它也存在以下缺点。

・继承复用破坏了类的封装性。

因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为

“白箱"复用。

・子类与父类的耦合度高。

父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。

•它限制了复用的灵活性。

从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

•合成复用

可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功

能,它有以下优点。

・它维持了类的封装性。

因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱"复用。

・新旧类之间的耦合度低。

这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。

・复用的灵活性高。

这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

•举例说明

・汽车分类管理

汽车按"动力源"划分可分为汽油汽车、电动汽车等;按"颜色"划分可分为白色汽车、

黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。

•继承方式实现

用继承关系实现会产生很多子类,而且增加新的“动力源"或者增加新的"颜色"都要修

改源代码,这违背了开闭原则,显然不可取。

・复合方式实现

・设计模式

・模式的分类

・按目的分类

主要用来完成什么工作来划分。

・创建型模式

主要用于创建对象

・工厂方法模式(FactoryMethod)

•抽象工厂模式(AbstractFactory)

•单例模式(Singleton)

•原型模式(Prototype)

・建造者模式(Builder)

・结构型模式

主要用于处理类或对象的组合

・适配器模式(Adapter)

•桥接模式(Bridge)

•组合模式(Composite)

•装饰模式(Decorator)

・外观模式(Facade)

•享元模式(Flyweight)

・代理模式(Proxy)

•行为型模式

主要用于描述对类或对象怎样交互和怎样分配职责

•职责链模式(ChainofResponsibility)

•命令模式(Command)

•解释器模式(Interpreter)

•迭代器模式(Iterator)

•中介者模式(Mediator)

•备忘录模式(Memento)

•观察者模式(Observer)

•状态模式(State)

・策略模式(Strategy)

•模板方法模式(TemplateMethod)

・访问者模式(Visitor)

・按范围分类

主要用于类上还是主要用于对象上来分.

・类模式

处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是属于静

态的.

・工厂方法模式(FactoryMethod)

•适配器模式(Adapter)

•模板方法模式(TemplateMethod)

•解释器模式(Interpreter)

・对象模式

处理对象间的关系,这些关系在运行时刻变化,更具动态性。

•除类模式(4个)之外的19个模式都属对象模式

・创建型模式

主要关注点是“怎样创建对象?",它的主要特点是"将对象的创建与使用分离"。

・单例模式

指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

・结构图

单例类Singleton

-instance:SingletonO

访问类Client

-Singleton()

+getlnstance():Singleton#instance:Singleton

publicstaticsynchronizedSingletorgetlnstance(){4

if(instance==null){//用懒汉方式实现

instance=newSingleton();

{else{

System.out.println(“单例对象已经创建!”);

}

returninstance;

J

•特点

・单例类只有一个实例对象;

・该单例对象必须由单例类自行创建;

•单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它;

・单例类向外提供一个静态的公有函数用于创建或获取该静态私有实例;

・目的

・保证一个类仅有一个实例,并提供一个访问它的全局访问点

•优点

•可以保证内存里只有一个实例,减少了内存的开销。

・设置全局访问点,可以优化和共享资源的访问。

・可以避免对资源的多重占用。

•缺点

・单例模式一般没有接口,扩展困难。

如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则.

・单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责

原则。

・适用场景

・需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少GC。

・某类只要求生成一个对象的时候,如一个班中的班长。

・某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。

・某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连

接池等。

・频繁访问数据库或文件的对象。

・当对象需要被共享的场合。

由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。

如Web中的配置对象、数据库的连接池等。

・实现方式

・方式一:懒汉式

该模式的特点是类加载时没有生成单例,只有当第一次调用getlnstance方法时才去创

建这个单例。

•实现方式

・普通方式

IpublicclassL纸丫负口吕1d9。(

privatestaticLazySingletoninstance=null;

privateLazySingletonO{}

publicstaticLazySingletone**etln...s..t.a...n.ceO{

if(instance==null){

instance=newLazySingletonO;

)

returninstance;

)

)

多线程下,会出线程安全问题。

・阻塞方式

publicclassLazySingleton{

〃保证instance在所有线程中同步

privatestaticvolatileLazySingletoninstance=null,

privateLazySingletonO0//priva.例化

publicstaticsynchronizedLazySingletongetlnstanceO{

//getInstance方法前加同步

if(instance--null){

instance=newLazySingletonO:

)

returninstance.

)

)

解决了线程安全问题,但是每次调用getlnstance()时都需要进行线程锁定判断,

在多线程高并发环境中,将会导致系统性能大大降低。

・volatile

synchronized是阻塞式同步,在线程竞争激烈的情况下会升级为重量级锁。

而volatile可以说是java虚拟机提供的最轻量级的同步机制。Java内存模

型告诉我们,各个线程会将共享变量从主内存中拷贝到工作内存,然后执

行引擎会基于工作内存中的数据进行操作处理。线程在工作内存进行操作

后何时会写到主内存中?这个时机对普通变量是没有规定的,而针对

volatile修饰的变量给java虚拟机特殊的约定,线程对volatile变量的修

改会立刻被其他线程所感知,即不会出现数据脏读的现象,从而保证数据

的“可见性"。简单说:被volatile修饰的变量能够保证每个线程能够获

取该变量的最新值,从而避免出现数据脏读的现象。

•保证了不同线程对这个变量进行操作时的可见性。(实现可见性)

即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

•禁止进行指令重排序。(实现有序性)

•volatile只能保证对单次读/写的原子性。i++这种操作不能保证原子

性。(实现原子性)

・双检验方式

publicclassL?碎兔醺]£必9(

privatestaticLazySingletoninstance=null;

privateLazySingletonO[}

publicstaticLazySingletongetJn?tanceO(

if(instance==null){

//该方法是静态方法,俘在方法区并且整个JVM只有一份,所以要加类锁

synchronized(LazySingleton.class^I

instance=newLazySingletonO,

)

)

returninstance

-)

由于JVM编译器的指令重排机制,同样会出现问题。

•问题:当进行LazySingletoninstance=newLazySingleton。;时,JVM

会进行下面的1,2,3操作,其中2,3的操作JI顺序可能颠倒。

线程0线程1

时间

判断instance是否为null

线程1初次访问对象

•反射攻击解决方案

在Java语言中,不仅可以通过new关键字直接创建对象,还可以通过反射机制创

建对象。即使访问权限是private,也可以通过setAccessible。来启动和禁用访问

安全检查的开关,参数值为true则指示反射的对象在使用时应该取消Java语言访

问检查。

・问题示例

publicstaticvc-i'in)ain(String[]args)throwsException(

Constructor<LazySingleton>c=LazySingleton.class.getDeclaredConstructor0

〃号数值为true则指示反射的对象由史用时应该取消Java谱存访问检在

c.setAccessible(true),

LazySingletoninstancel=c.newInstanceO:

LazySingletoninstance?=LazySingleton.gefJnsranceO,

System.?ut.println(instancel)

System,our.println(instance2)

System,out.println(instancel=instance?):

)

MainTestmain。

MainTest

D:\Java\jdkl.8.0_211\bin\java.exe

com.jr.demo.LazySingleton042110106

com.jr.demo.LazySingleton0531d:2ca

false

・解决方案

publicclassLazySingleton(

privatestaticLazySingletoninstance=null.

privatestaticbooleanflag=false;

pflvaieLazySlngleionO(

if(flag){//避免第•次创建实例的是:反射

thrownewRuntimeException'<'''/::nY匚;;'):

)

if(instance1=null){

thrownewRuntimeExceptionC'.;>iiih'-1d;

)

flag=true;

J__________________________________________________________________

publicstaticLazySingletongetlnstanceO(

在私有构造器里面进行判断,如果instance对象已经实例化了,就抛出异常。

但是如果先通过反射创建一个对象,创建后的instance还是null,这个时候通

过getlnstanceO获取instance对象会创建第二个。所以还要通过设置标志位

的方式来解决这个问题。

•序列化破坏解决方案

・序列化破坏单例模式示例

publicstaticvoidmain(String口args)throwsException{

LazySingletoninstancel=LazySingleton.getlnstance()

//使用序列化和反序列化创建实例

ByteArrayOutputStrearobos=newByteArrayOutputStrearn0.

ObjectOutputStreamoos=newObjectOutputStream(bos)

oos.writeObject(instancel)

byte[]bytes=bos.toByteArrayO

ByteArrayInputStreambis=newByteArrayinputStream(bytes):

ObjectInputStreamois=newObjectlnputStream(bis)

LazySingletoninstance?=(LazySingleton)ois.readObject0,

System,out.printIn(instancel).

System,out.printIn(instance?);

System,out.printIn(instancel=instance2)|

)

MainTestmainO

MainTest

D:\Java\jdkl.8.0_211\bin\java.exe...

com.jr.demo.LazySingleton@255316f2

com.jr.demo.LazySingleton©142d9b6e

false

序列化和反序列化操作时,每次反序列化一个序列化的实例时,都会创建一个

新的实例。

•解决方案

想将单例类变成可序列化的,仅在声明上加上implementsSerializable是不

够的,为了维护并保证单例,必须声明所有实例域都是瞬时(transient)的,

并提供一个readResolve方法。

・1.实现Serializable接口;

单例类变成可序列化,必须实现序列化接口;

•2.使用transient声明实例域;

序列化形式并不需要包含任何实际的数据,所有的实例域都应该被声明为

瞬时的。如果依赖readResolve进行实例控制,带有对引用类型的所有实

例域必须transient,否则,就有可能在readResolve方法被运行之前,

保护指向反序列化对象的引用。

•3.提供一个readResolve方法;

对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方

法,那么在反序列化之后,新建对象上的readResolve方法就会被调用,

然后该方法返回的对象引用将被返回,取代新建的对象,新建对象的引用

不需要再被保留,因此立即成为垃圾回收的对象。

・代码示例

•单例类

.icclassLazySingletoniiuplementsSerializable{

privatestatic]transient]azySingletoninstance=null.

privatestaticbooleanflag=false:

privateObjectreadResolveO{

returninstance.

•测试类

puLlistati-vc-i.]main<String[]args)throwException{

LazySingletoninstancel=LazySingleton.getInstanceO.

11使用序列化和反序列化创建实例

ByteArrayOutputStreambos=newByteArrayOutputStream()

ObjectOutputStreamoos=newObjectOutputStreeun(bos).

oos.writeObject(instancel)

byte口bytes=bos.toByteArray()

ByteArrayinputStreambis=newByteArrayinputStream(bytes);

ObjectInputStreamois=newObjectInputStream(bis).

LazySingletoninstance2=(LazySingleton)ois.readObject0

System,out.println(instancel)

System,out.println(instance2).

System,cut.printIn(instancel=instance?):

)

MainTestmain。

MainTest

D:\Java\jdkl.8.0_211\bin\java.exe...

com.jr.demo.LazySingleton@255316f2

com.jr.demo.LazySingleton@255316f2

true

・方式二:饿汉式

类加载的时候就创建一个实例,在调用getlnstance方法之前实例已经存在了。无线

程安全问题。

•static特性

・static变量在类装载的时候进行初始化

・多个实例的static变量会共享同一块内存区域

•代码示例

publicclass晒1电丫显举]士[Qp{

privatestaticfinalHungrySing1etoninstance=newHungrySingletonO

privateHungrySingletonO[}

publicstaticHungrySingletongetInstanggO(

returninstance,

)

)

•反射攻击解决方案

publicclassHungrySing1eton(

privatestaticfinalHungrySing1etoninstance=newHungrySingleton0;

Iprivate"HungfySingleton(?t

if(instance1=null){

thrownewRuntimeException(「飞鸟造*;1/TJ!!):|

)

|}|

publicstaticHungrySing1etongetlnstanceO{

returninstance;

)}

私有构造函数增加判断。

•序列化破坏解决方案

•1.实现Serializable接口;

单例类变成可序列化,必须实现序列化接口;

•2.使用transient声明实例域;

序列化形式并不需要包含任何实际的数据,所有的实例域都应该被声明为瞬时

的。如果依赖readResolve进行实例控制,带有对引用类型的所有实例域必

须transient,否则,就有可能在readResolve方法被运行之前,保护指向

反序列化对象的引用。

•3.提供一个readResolve方法;

对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方法,

那么在反序列化之后,新建对象上的readResolve方法就会被调用,然后该方

法返回的对象引用将被返回,取代新建的对象,新建对象的引用不需要再被保

留,因此立即成为垃圾回收的对象。

・方式三:静态内部类

饿汉式不能实现延迟加载,始终占据内存;而懒汉式线程安全控制繁琐麻烦,而且性能

也会受到影响。静态内部类单例方式,能够将二者的缺点克服而兼顾优点,即可以做

到延迟加载,且线程安全。

・静态内部类的属性

・由static修饰的成员式内部类,它的对象与外部类对象不发生依赖关系,其相

当于其外部类的成员。

•外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内

部类和静态内部类。

・静态内部类只有被调用时才会被加载,从而实现了延迟加载。

・代码示例

・静态内部类

publicclass§1旦!icJnncrClassSjneleiQn(

privateStaticInnerClassSingletonO0

privatestaticclassInnerClass(

privatestaticStaticInnerClassSingleton:nsrance=newStaticInnerClassSingletonO

)

publicstat•<:StaticInnerClassSingleton工(

returnInnerClass.instance

)

)

当第一次调用getlnstance()时,它第一次读取InnerClass.instance,导致

InnerClass内部类得到初始化,而这个类在装载并被初始化的时候,会初始化

它的静态域。从而创建了StaticInnerClassSingleton的实例,由于是静态的

属性,因此只会在虚拟机装载类的时候初始化一次,并由JVM来保证它的线程

安全性。

・反射攻击解决方案

・私有构造函数判空

publicclassSti.以段.式]as$SieIQR(

privateStaticInnerdlassSingletonOt

Iif(InnerClass.instance1=null)(

thrownewRuntineExceptiond'例构巨器力I—;I

)I))I

privatestaticclassInnerClass(

privatestaticStaticInnerClassSingletoninstance=newStaticInnerClassSingletonO.

)}

publicstaticStaticInnerClassSingletongetlmceO(

returnInnerClass.instance.

)

)

・序列化破坏解决方案

与懒汉式一致

・1.实现Serializable接口;

单例类变成可序列化,必须实现序列化接口;

•2.使用transient声明实例域;

序列化形式并不需要包含任何实际的数据,所有的实例域都应该被声明为

瞬时的。如果依赖readResolve进行实例控制,带有对引用类型的所有实

例域必须transient,否则,就有可能在readResolve方法被运行之前,

保护指向反序列化对象的引用。

•3.提供一个readResolve方法;

对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方

法,那么在反序列化之后,新建对象上的readResolve方法就会被调用,

然后该方法返回的对象引用将被返回,取代新建的对象,新建对象的引用

不需要再被保留,因此立即成为垃圾回收的对象。

•方式四:枚举

《effectiveJava》第三版中提到:单元素枚举类型经常成为实现Singleton的最住

方法.使用枚举来实现单例模式会非常简洁,而且提供了序列化的机制,并由JVM从

根本上提供保障,绝对防止多次实例化,并且天然线程安全,是更简洁,高效,安全的

方式

・枚举类特性

•使用enum定义的枚举类默认继承了java.lang.Enum类,因此不能再继承其

他类;

•枚举类的构造器只能使用private权限修饰符;

•枚举类的所有实例必须在枚举类中显式列出,分隔;结尾)。列出的实例系统会

自动添加publicstaticfinal修饰;

•必须在枚举类的第一行声明枚举类对象;

・枚举类常用方法

•values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。

•valueOf(Stringstr):可以把一个字符串转为对应的枚举类对象。要求字符串

必须是枚举类对象的"名字"。如不是,会有运行时异常:

IllegalArgumentExceptiono

•toString():返回当前枚举类对象常量的名称。

•代码示例

・单元素枚举

publicenuinEnumlnstance{

INSTANCE,

privateUseruser;

privateEnumlnstance0{

user=newUser0;

user.setName(,jack');

I)

publicUsergetUser0{

returnuser;

I)

publicstaticEnumlnstancegetlnstanceO(

returnINSTANCE-

I)

)

•测试

publicjtaiicvoidmain(Siring口args)throwsException(

Enumlnstanceinstance】=Enumlnstance.getlnsfanceO

Enunilnstanceinstance2=Enumlnstance.getlnstanceO

System,our.printlnt|+(instancel.getUser()==instance2.getUserO))

//序列化破坏

ByteArrayOutputStreambos=newByleArrayOutputStrejjtnO,

ObjectOutputStreamoos=newObjectOutputStream(bos)

oos.writeObject(instancel),

byte[]bytes=bos.toByteArray(),

ByteArraylnputStreambis=newByteArraylnputStream(bytes)

ObjectInputStreamois=newObjectInputStream(bis),

Enumlnstanceinstance3=(Enumlnstance)ois.readObject()

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论