觉得不错请按下图操作,掘友们,哈哈哈!!!
概述规划形式分类
整体来说规划形式分为三大类:
创立型形式,共五种:工厂办法形式、笼统工厂形式、单例形式、制作者形式、原型形式。
结构型形式,共七种:适配器形式、装饰器形式、署理形式、外观形式、桥接形式、组合形式、享元形式。
行为型形式,共十一种:策略形式、模板办法形式、观察者形式、迭代子形式、责任链形式、命令形式、备忘录形式、状态形式、拜访者形式、中介者形式、解释器形式。
一:单例形式
1.1 名词解释
简单来说单例形式是指在内存中只会创立且仅创立一次方针的规划形式。在程序中屡次运用同一个方针且作用相同时,为了防止频频地创立方针使得内存飙升,单例形式能够让程序仅在内存中创立一个方针,让一切需求调用的当地都同享这一单例方针。
1.2 优缺陷
单例形式的长处:
- 单例形式能够保证内存里只要一个实例,削减了内存的开销。
- 能够防止对资源的多重占用。
- 单例形式设置大局拜访点,能够优化和同享资源的拜访。
单例形式的缺陷:
- 单例形式一般没有接口,扩展困难。假如要扩展,则除了修正原来的代码,没有第二种途径,违反开闭准则。
- 在并发测验中,单例形式不利于代码调试。在调试过程中,假如单例中的代码没有执行完,也不能模仿生成一个新的方针。
- 单例形式的功能代码一般写在一个类中,假如功能规划不合理,则很容易违反单一责任准则。
1.3 适用场景
在 Java中,单例形式能够保证在一个 JVM 中只存在单一实例。单例形式的运用场景主要有以下几个方面。
- 需求频频创立的一些类,运用单例能够下降体系的内存压力,削减 GC。
- 某类只要求生成一个方针的时分。
- 某些类创立实例时占用资源较多,或实例化耗时较长,且常常运用。
- 某类需求频频实例化,而创立的方针又频频被毁掉的时分,如多线程的线程池、网络连接池等。
- 频频拜访数据库或文件的方针。
- 关于一些操控硬件等级的操作,或许从体系上来讲应当是单一操控逻辑的操作,假如有多个实例,则体系会彻底杂乱。
- 当方针需求被同享的场合。由于单例形式只允许创立一个方针,同享该方针能够节省内存,并加快方针拜访速度。如 Web 中的装备方针、数据库的连接池等。
1.4 完成方法
好了,既然咱们现已知道什么是单例,以及单例形式的优缺陷,那咱们接下来继续讨论下怎么完成单例。
一般来说,咱们能够把单例分为行为上的单例和管理上的单例。行为上的单例代表不管怎么操作,在jvm层面上都只要一个类的实例,而管理上的单例则能够理解为:不管谁去运用这个类,都要守一定的规矩,比方说,咱们运用某个类,只能从指定的当地’去拿‘,这样拿到便是同一个类了。
而关于管理上的单例,相信咱们最为熟悉的便是spring了,spring将一切的类放到一个容器中,今后运用该类都从该容器去取,这样就保证了单例。
所以这里咱们剩余的便是接着来谈谈怎么完成行为上的单例了。一般来说,这种单例完成有两种思路,私有结构器,枚举。
单例形式有两种类型:
- 懒汉式:在真正需求运用方针的时分才会去创立该方针;
- 饿汉式:在类加载时创立好该单例方针,等候被程序运用。
1.5 上demo
饿汉形式:
IvoryTower :单例类。初始化的静态实例保证线程安全
public final class IvoryTower {
/**
* 私有结构函数,因而没有人能够实例化该类.
*/
private IvoryTower() {
}
/**
* 静态到类的类实例.
*/
private static final IvoryTower INSTANCE = new IvoryTower();
/**
* 被用户调用以获取类的实例.
*
* @return instance of the singleton.
*/
public static IvoryTower getInstance() {
return INSTANCE;
}
}
懒汉形式初始化单例:
ThreadSafeLazyLoadedIvoryTower:线程安全的单例类。实例被慵懒初始化,因而需求同步机制
public final class ThreadSafeLazyLoadedIvoryTower {
private static volatile ThreadSafeLazyLoadedIvoryTower instance;
private ThreadSafeLazyLoadedIvoryTower() {
// 经过反射,防止实例化化
if (instance != null) {
throw new IllegalStateException("Already initialized.");
}
}
/**
* 在第一次调用办法之前不会创立实例
*/
public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {
if (instance == null) {
instance = new ThreadSafeLazyLoadedIvoryTower();
}
return instance;
}
}
是按需初始化的单例完成。缺陷是拜访速度很慢,由于整个拜访办法是同步的
经过枚举完成单例:
EnumIvoryTower: 此完成是线程安全的,可是添加任何其他办法及其线程安全是开发所做的
public enum EnumIvoryTower {
INSTANCE;
@Override
public String toString() {
return getDeclaringClass().getCanonicalName() + "@" + hashCode();
}
}
两层查看确定完成:
public final class ThreadSafeDoubleCheckLocking {
private static volatile ThreadSafeDoubleCheckLocking instance;
/**
* 私有结构函数以防止客户端实例化.
*/
private ThreadSafeDoubleCheckLocking() {
// to prevent instantiating by Reflection call
if (instance != null) {
throw new IllegalStateException("Already initialized.");
}
}
/**
* 公共拜访器
*
* @return an instance of the class.
*/
public static ThreadSafeDoubleCheckLocking getInstance() {
// 局部变量将功能提高 25%
// Joshua Bloch “Effective Java,第二版”,p. 283-284
var result = instance;
// 查看单例实例是否初始化.
// 假如它已初始化,那么咱们能够返回实例。
if (result == null) {
// 它没有初始化,但咱们不能确定,由于其他线程可能有
// 同时初始化它.
// 所以为了保证咱们需求确定一个方针以取得互斥.
synchronized (ThreadSafeDoubleCheckLocking.class) {
//再次将实例分配给局部变量以查看它是否由其他当地初始化
//当时线程被阻塞进入确定区时其他线程
// 假如它已被初始化,那么咱们能够返回之前创立的实例
// 就像之前的空查看相同。
result = instance;
if (result == null) {
// 该实例仍未初始化,因而咱们能够安全地创立实例
// (没有其他线程能够进入这个区域)
// 创立一个实例并使其成为咱们的单例实例.
instance = result = new ThreadSafeDoubleCheckLocking();
}
}
}
return result;
}
}
它比 ThreadSafeLazyLoadedIvoryTower 快一些,由于它不同步整个拜访办法,而只同步特定条件下的办法内部。
按需初始化 holder:
能够找到另一种完成线程安全的推迟初始化单例的办法。可是,此完成至少需求 Java 8 API 等级才能作业
public final class InitializingOnDemandHolderIdiom {
/**
* 私有结构函数.
*/
private InitializingOnDemandHolderIdiom() {
}
/**
* 单例实例.
*
* @return Singleton instance
*/
public static InitializingOnDemandHolderIdiom getInstance() {
return HelperHolder.INSTANCE;
}
/**
* 供给推迟加载的 Singleton 实例.
*/
private static class HelperHolder {
private static final InitializingOnDemandHolderIdiom INSTANCE =
new InitializingOnDemandHolderIdiom();
}
}
Initialize-on-demand-holder 习惯用法是在 Java 中创立慵懒初始化单例方针的安全办法;该技能尽可能慵懒,适用于一切已知的 Java 版别。它运用 关于类初始化的语言保证,因而能够在一切兼容 Java 的编译器和虚拟机中正常作业;部类的引证时间不早于调用 getInstance() 的时间(因而类加载器加载时间也不早)。因而,此处理方案是线程安全的,不需求需求特别的语言结构。
程序进口:
@Slf4j
public class App {
public static void main(String[] args) {
// 饿汉形式
var ivoryTower1 = IvoryTower.getInstance();
var ivoryTower2 = IvoryTower.getInstance();
LOGGER.info("ivoryTower1={}", ivoryTower1);
LOGGER.info("ivoryTower2={}", ivoryTower2);
// 懒汉形式
var threadSafeIvoryTower1 = ThreadSafeLazyLoadedIvoryTower.getInstance();
var threadSafeIvoryTower2 = ThreadSafeLazyLoadedIvoryTower.getInstance();
LOGGER.info("threadSafeIvoryTower1={}", threadSafeIvoryTower1);
LOGGER.info("threadSafeIvoryTower2={}", threadSafeIvoryTower2);
// 枚举单例
var enumIvoryTower1 = EnumIvoryTower.INSTANCE;
var enumIvoryTower2 = EnumIvoryTower.INSTANCE;
LOGGER.info("enumIvoryTower1={}", enumIvoryTower1);
LOGGER.info("enumIvoryTower2={}", enumIvoryTower2);
// 两层查看确定完成
var dcl1 = ThreadSafeDoubleCheckLocking.getInstance();
LOGGER.info(dcl1.toString());
var dcl2 = ThreadSafeDoubleCheckLocking.getInstance();
LOGGER.info(dcl2.toString());
// 按需初始化 holder
var demandHolderIdiom = InitializingOnDemandHolderIdiom.getInstance();
LOGGER.info(demandHolderIdiom.toString());
var demandHolderIdiom2 = InitializingOnDemandHolderIdiom.getInstance();
LOGGER.info(demandHolderIdiom2.toString());
}
}
二:适配器形式
2.1 名词解释
适配器形式属于“结构型形式”的一种。该形式的中心思维便是将类华夏本不适合当时客户端运用的接口转换成适用的接口,然后大大提高程序的兼容性。并且从运用者的视点是看不到被适配的类地,也下降了代码之间的耦合性。适配器形式的作业原理便是:用户调用适配器转换出来的接口,适配器在调用被适配类的相关接口,然后完成适配。
2.2 优缺陷
长处:
- 客户端经过适配器能够透明地调用方针接口。
- 复用了现存的类,程序员不需求修正原有代码而重用现有的适配者类。
- 将方针类和适配者类解耦,处理了方针类和适配者类接口不一致的问题。
- 在许多事务场景中契合开闭准则。
缺陷:
- 适配器编写过程需求结合事务场景全面考虑,可能会增加体系的复杂性。
- 增加代码阅读难度,下降代码可读性,过多运用适配器会使体系代码变得杂乱
2.3 适用场景
-
封装有缺陷的接口规划: 假定咱们依靠的外部体系在接口规划方面有缺陷(比方包括大量静态办法),引入之后会影响到咱们本身代码的可测验性。为了阻隔规划上的缺陷,咱们希望对外部体系供给的接口进行二次封装,笼统出更好的接口规划,这个时分就能够运用适配器形式了。
-
一致多个类的接口规划: 某个功能的完成依靠多个外部体系(或许说类)。经过适配器形式,将它们的接口适配为一致的接口定义,然后咱们就能够运用多态的特性来复用代码逻辑。
-
替换依靠的外部体系: 当咱们把项目中依靠的一个外部体系替换为另一个外部体系的时分,运用适配器形式,能够削减对代码的改动。
-
兼容老版别接口: 在做版别晋级的时分,关于一些要废弃的接口,咱们不直接将其删除,而是暂时保留,并且标注为 deprecated,并将内部完成逻辑委托为新的接口完成。这样做的优点是,让运用它的项目有个过渡期,而不是强制进行代码修正。这也能够粗略地看作适配器形式的一个运用场景。
-
适配不同格局的数据: 适配器形式主要用于接口的适配,实际上,它还能够用在不同格局的数据之间的适配。比方,把从不同征信体系拉取的不同格局的征信数据,一致为相同的格局,以方便存储和运用。再比方,Java 中的 Arrays.asList() 也能够看作一种数据适配器,将数组类型的数据转化为集合容器类型。
2.4 完成方法
经过不同的完成方法,咱们能够将其分成三类: 类适配器形式,方针适配器形式,接口适配器形式。
适配器形式(Adapter)包括以下主要角色。
- 方针(Target)接口:当时体系事务所期待的接口,它能够是笼统类或接口。
- 适配者(Adaptee)类:它是被拜访和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,经过承继或引证适配者的方针,把适配者接口转换成方针接口,让客户按方针接口的格局拜访适配者。
2.5 上demo
这个故事是这样的。 海盗来了!咱们需求 RowingBoat(划艇) 来逃跑!咱们有一艘 FishingBoat(渔船)和咱们的船长。咱们没有时间去补新船!咱们需求重用这个FishingBoat(渔船)。船长需求一艘他能够操作的划艇。标准在 RowingBoat(划艇) 中。咱们将运用适配器形式来重用它。
RowingBoat:客户所希望的接口,一艘能够开的划艇
public interface RowingBoat {
void row();
}
Captain:船长运用 RowingBoat 飞行。 这是在这个形式中的客户
@Setter
@NoArgsConstructor
@AllArgsConstructor
public final class Captain {
private RowingBoat rowingBoat;
void row() {
rowingBoat.row();
}
}
FishingBoat:设备类(形式中的适配器)。咱们想重用这个类用渔船飞行。
@Slf4j
final class FishingBoat {
void sail() {
LOGGER.info("The fishing boat is sailing");
}
}
FishingBoatAdapter: 适配器类。将设备接口 FishingBoat 调整为 RowingBoat 客户所希望的接口
public class FishingBoatAdapter implements RowingBoat {
private final FishingBoat boat = new FishingBoat();
public final void row() {
boat.sail();
}
}
App:程序进口:
public final class App {
private App() {
}
/**
* 程序进口.
*
* @param args command line args
*/
public static void main(final String[] args) {
// 船长只能操作划艇,但经过适配器他能够
// 也运用渔船
var captain = new Captain(new FishingBoatAdapter());
captain.row();
}
}
本文正在参与「金石方案」