ARTS Week5

发布于: 2020 年 06 月 27 日

Algorithm

盛最多水的容器

此题用到了双指针,从数组两头开始计算。那么此时的宽是数组的长度,高是数组两个下标取最短。与初始化的最大面积比较,取最大。

然后是看左下标的值是否小于右下标,如果小于,那么左指针向右移;否则右指针向左移。因为如果是移动大的一边,小的一边还是不变,那么面积只能是小于等于原来的面积。

Review

SOLID Principles every Developer Should Know

每个开发者都必须知道 SOLID 原则。

SRP: 每个类都应该只有唯一的功能

当我们设计类的时候,应该着眼于将相同功能放在一起,它们会因为相同的原因而改变;否则,我们应该尝试将功能分开。

OCP:软件实体应该是通过扩展来实现变化

当我们需要增加某些功能某些变化的时候,应该是通过扩展的方式来实现的;而不是通过改变现有的代码来实现变化。

LSP:一个子类必然可以替代它的超类

里氏替代原则是继承复用的基石。只有当衍生类可以替代掉基类,并且软件单位的功能不会因此而受到影响时,基类才能被真正复用。

ISP:不强迫客户端依赖它不使用的接口

我们应该将接口按照不同客户端细化,创建专用的接口。而不是创建一个接口,将所有的方法都放进去,并要求所有实现这个接口的客户端都必须实现所有的方法。

DIP:依赖性应该是抽象而不是具体的东西

A. 高层模块不应依赖于低层模块。两者都应该依赖于抽象。

B. 抽象不应依赖于细节。细节应该依赖于抽象。

Tip

Java中的单例模式

饿汉模式

饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。它的优点是它只会在类加载的时候创建一次;但是缺点同样明显,即使没有用到这个实例也会被创建,浪费了内存。

public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}

懒汉模式

懒汉模式中单例是在需要的时候才去创建的。但是在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例。

public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}

所以,为了解决线程同步问题,我们需要加锁来解决

public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton newInstance(){
if(null == instance){ // Single Checked
instance = new Singleton();
}
return instance;
}

双重校验锁

上面加锁的懒汉模式虽然解决了同步的问题,又解决了延迟加载的问题,但是依然存在性能问题。synchronized修饰的同步方法比一般方法要慢很多,如果多次调用getInstance(),累积的性能损耗就比较大了

public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (null == instance) { // Single Checked
synchronized(Singleton.claass) {
if (null == instance) { // Double Checked
instance = new Singleton();
}
}
}
return instance;
}
}

双重校验锁即实现了延迟加载,又解决了线程并发问题,同时还解决了执行效率问题。然而双重校验锁还是存在着缺陷。

指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快

由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。

所以有一种情况发生:

一个线程创建单例对象,然而没有初始化之前,就为该对象分配了内存空间并为该对象的字段设置默认值;此时如果有另一个进程前来访问,那么调用getInstance,就会因为没有初始化而获取状态不正确的对象,就会报错。

解决方法:

volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。

public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (null == instance) { // Single Checked
synchronized(Singleton.claass) {
if (null == instance) { // Double Checked
instance = new Singleton();
}
}
}
return instance;
}
}

静态内部类【推荐】

系统启动时内部类是不会加载的。但当内部类第一次被调用时,则会成功初始化实例

public class Singleton {
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

可以看到内部类 SingletonHolder 也使用了饿汉模式加载。前面说过饿汉模式的缺点是即使没有用到这个实例也会被创建,浪费了内存。

但是在内部类中,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全

Share

识别代码中的坏味道

  1. 在开发过程中,我们应该尽量避免重复代码或者相同逻辑的出现,因为重复代码会很容易造成修改遗漏。

  2. 避免长函数,因为他的可读性非常低。横向过长的代码,可以通过代码格式化、CheckStyle插件来发现和消除。纵向过长的代码,可以通过分解提炼成多个小函数。

  3. 过大的类。当一个类过大时,可能是因为这个类负责的职务太过复杂,不符合SRP原则。应该尝试把类分解成多个职责单一的类。

  4. 过长的参数列表。首先就是可读性差;其次就是如果需要添加新的参数,会让调用方也发生变化。可以通过传入一个包含多个属性的参数对象

  5. 避免无用的注释。比如没有用的逻辑代码,误导性注释,日志式注释等等。

用户头像

时之虫

关注

还未添加个人签名 2020.05.25 加入

还未添加个人简介

评论

发布
暂无评论
ARTS Week5