构造方法必须无代码
来源:互联网 发布:思迅软件怎么样 编辑:程序博客网 时间:2024/06/03 22:39
在一个构造方法里应该完成多少工作?在构造方法里进行一些运算然后封装结果似乎有些道理。那样的话,当对象方法需要结果时,我们已经准备好了。听起来像是一个好方法。不,不是。这是一个坏主意的原因之一是:它阻止了对象组合并且让它们无法扩展。
现在我们做一个展现一个人名字的接口:
interface Name { String first();}
相当简单,对吧?现在,我们试着实现它:
public final class EnglishName implements Name { private final String name; public EnglishName(final CharSequence text) { this.name= text.toString().split(" ", 2)[0]; } @Override public String first() { return this.name; }
这样做有什么问题吗?它更快,对吧?它一次就把name分割成多个部分并封装了它们。然后,不管我们调用first()方法多少次,它都会返回相同的值并且我们不需要再次进行分割。然后,这个想法有缺陷。让我向你展示正确的方式和解释:
public final class EnglishName implements Name { private final CharSequence text; public EnglishName(final CharSequence txt) { this.text = txt; } @Override public String first() { return this.text.toString().split("", 2)[0]; }}
这是正确的设计。我能看见正在微笑,所以让我来证明我的观点。
在我开始证明之前,请先阅读这编文章:组合修饰符 vs. 必要实用方法
。文章解释了静态方法与可组合修饰符的区别。上面的第一段代码非常接近一个必要实用方法,即使它看起来像一个对象。第二个例子才是一个真正的对象。
在第一个例子里面,我们滥用了new操作符并且把它转换成了一个静态方法,所有的运算都在里面立刻进行。这就是命令式编程。在命令式编程中,我们立即进行所有的运算并返回完全准备好的结果。相反,在声明式编程中,我们尽可能的推迟计算。
让我们尝试使用EnglishName类:
final Name name = new EnglishName( new NameInPostgreSQL(/*...*/));if (/* something goes wrong */) { throw new IllegalStateException( String.format( "Hi, %s, we can't proceed with your application", name.first() ) );}
代码片段的第一行,我们只是创建了一个对象的实例并命名为name。我们还不想去数据库,把从那里获取的全名,分割成部分,并且把它们包装在name里面。我们只是想创建一个对象的实例。这样的一个解析行为会有副作用,那样的话,会拖慢现有应用程序。正如你所看到的,我们只需要name.first(),如果出了问题,我们需要构造一个异常对象。
我的观点是在构造方法里进行任何运算是一个不好的实践并且必须避免,因为有副作用并且不是对象拥有者所需要的。
你可能会问,在name的重用期间性能怎么样呢?如果我们创建一个EnglishName的实例并且调用name.fist()方法5次,我们最终会调用5次String.split()方法。
为了解决这个问题,我们创建另一个类,一个组合修饰符,会帮助我们解决这个重用问题。
public final class CachedName implements Name { private final Name origin; public CachedName(final Name name) { this.origin = name; } @Override @Cacheable(forever = true) public String first() { return this.origin.first(); }}
我使用的是http://aspects.jcabi.com/
里面的Cacheable注解,但是你能使用任何其他的Java缓存工具。
public final class CachedName implements Name { private final Cache<Long, String> cache = CacheBuilder.newBuilder().build(); private final Name origin; public CachedName(final Name name) { this.origin = name; } @Override public String first() { return this.cache.get( 1L, new Callable<String>() { @Override public String call() { return CachedName.this.origin.first(); } } ); }}
但是请别让CachedName可变和延迟加载—它是一个反面模式,这个之前已经在 对象应该不变 中讨论过。
我们的代码现在像这样:
final Name name = new CachedName( new EnglishName( new NameInPostgreSQL(/*...*/) ));
这是一个非常原始的例子,但我希望你能明白。
在这个设计中,我们基本上把对象分割成了两部分。第一部分知道怎样在英文名字中拿到first name.第二知道怎样在内存中缓存运算结果。我决定我是否需要缓存。这就是对象组合。
让我重申在构造方法里唯一允许的声明就是赋值。如果你需要在构造方法里放其他东西,开始思考重构吧—–你的类肯定需要重新设计。
- 构造方法必须无代码
- 为什么Java写个带参的构造方法,那无参的就必须再显示写一个
- 无参构造方法
- 13.9 Swift必须构造方法
- 构造方法与构造代码
- 父类不含无参构造方法子类构造方法必须显式调用
- java 无参、有参构造方法;静态、非静态代码块执行顺序
- java有参构造方法和无参构造方法
- java有参构造方法和无参构造方法
- 父子类静态代码块,非静态代码块,有参,无参构造方法等的执行顺序问题
- 无参和有参构造方法
- java 反射操作无参构造方法
- 子类无参构造调用父类有参构造方法
- 定义无参构造和有参构造方法
- 【JAVA_SE】构造方法与构造代码块
- java_构造方法、代码块
- 编译错误:构造方法必须是第一个语句
- J2ME中主类的构造方法必须用public修饰
- iOS沙盒机制
- cocos2d-x 下载
- hive 基础以及建议
- Android自定义view 滑动开关 支持左右滑动 适用于listview
- iOS调用系统通讯录获取姓名电话号码
- 构造方法必须无代码
- 第40讲--项目二--水仙花数
- poj 1961 Period 【前缀循环节】
- 开启记录自己的开发旅程
- mysql programs(一)--mysqlshow
- 项目中问题总结
- 使用 Minidumps 和 Visual Studio .NET 进行崩溃后调试
- 大二下期ACM比赛前感想
- 针对toy datasets的不同聚类方法比较