前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >神奇SELF-TYPE:让你的类更精简的一种方式

神奇SELF-TYPE:让你的类更精简的一种方式

作者头像
用户2936994
发布2022-07-21 13:45:30
2680
发布2022-07-21 13:45:30
举报
文章被收录于专栏:祝威廉祝威廉

本来标题名想取 神奇SELF-TYPE:继承,Mixin和对象组合之外的类交互方式的,但是发现不容易理解,找了半天,觉得还是现在的标题好

我们经常会把一个类写的很大,因为我们要完成的任务非常多。但是一个类过于庞大,往往会有巨大的维护成本。 所以面向对象编程引入多个类来将单个类拆解,从而使得代码的组织变得更加优雅,但这也引入了一个新的问题,就是,如何让这些类进行协作交互。我们首先会想到如下两个:

  1. 继承
  2. Mixin(拥有方法的Trait)

接着,我们还会有下面的办法:

  1. 静态工具类(本质是以函数为粒度封装,然后通过一个object/static class 来进行管理)
  2. 工具对象,在使用时new出来,然后调用里面的逻辑。

其中,继承和mixin可以将被继承的类和被mixin的类的成员(变量以及方法)引入到继承者身上,好处是可以方便的在主类里访问到这些方法,而静态工具类和工具对象,则更加独立,复用程度也更好,缺点是成员可见性问题,会使得方法签名或者对象实例属性变得很复杂。

下面我们一个一个来看。

代码语言:javascript
复制
class A(v1:String,v2:String) {
   def complexFun()={
     val v11 = process1(v1)
     val v12 = process2(v1,v2)
     compose(v11,v12)
   }
}

process1/process2/compose 三个方法里的逻辑都可以放到A里,不过假设他们逻辑其实非常复杂,而且其他地方也会需要用到,所以这个时候,我们可以将其抽取为静态工具类

代码语言:javascript
复制
object Process1 {
  def process1(v1:String) .... 
}
object Process2 {
  def process2(v1:String) .... 
}

class A 需要改成如下的样子了:

代码语言:javascript
复制
class A(v1:String,v2:String) {
   def complexFun()={
     val v11 = Process1.process1(v1)
     val v12 = Process2.process2(v1,v2)
     compose(v11,v12)
   }
}

现在看起来一切都很好,但是如果process2需要很多东西,事情就会变得复杂了,参数变得很多就会很难受。

代码语言:javascript
复制
class A(v1:String,v2:String) {
   val v2 = ...
   private def v3 = ...
   .....
   def complexFun()={
     ....
     val v12 = Process2.process2(v1,v2,v3,v4,v5,v6)
     ....
   }
}

这个时候,我们可以抽象成对象,部分变成实例变量,部分变成参数来减少这种难受:

代码语言:javascript
复制
class A(v1:String,v2:String) {
   val v2 = ...
   private def v3 = ...
   .....
   def complexFun()={
     ....
    val p2 = new Process2(v1,v2)
     val v12 = p2.process2(v3,v4,v5,v6)
     ....
   }
}

但是,这个时候A又加了一个变量v7,我们也需要在Process2里访问这个变量,你会觉得太麻烦了,还要纠结放到实例变量还是方法参数里。索性将A作为变量传递进去:

代码语言:javascript
复制
class A(v1:String,v2:String) {
   val v2 = ...
   private def v3 = ...
   .....
   def complexFun()={
     ....
    val p2 = new Process2(this)
     val v12 = p2.process2()
     ....
   }
}

舒服很多了,但是你会发现在Process2里,没办法访问A的private 函数v3,于是你不得不修改他的范围,就是为了能够在Process2里去访问v3。而且,你的Process2不再变得那么复用了,他被绑定到了A中,为了使用Process2,你必须实例化一个A,并且确保A里的东西都能被Process2所访问到。 如果我们直接继承Process2,则避免了这个麻烦。

代码语言:javascript
复制
class A(v1:String,v2:String) extends  Process2{
   val v2 = ...
   private def v3 = ...
   .....
   def complexFun()={
     ....
    process2(v1...v7)
     ....
   }
}

但是问题来了,我们没办法在Process2的方法里访问A的变量,因为Process2对A 一无所知,于是我们又回到了通过参数传递变量的方法里去了。

这个时候,我们希望能够找到一种更好的类组织方式,我们希望能够把代码分门别类的放到不同的类里面,但是他们能够自由的访问住类的变量,使用起来看起来就像一个类一样,避免复杂方法或者实例调用。 Scala 提供这种问题的解决方案,叫Self-Type,极大的简化了代码的组织。

代码语言:javascript
复制
class DeltaLog private(
    val logPath: Path,
    val dataPath: Path,
    val clock: Clock)
  extends Checkpoints
  with MetadataCleanup
  with LogStoreProvider
  with VerifyChecksum {

我们看这个例子,我们定义了三个实例变量,然后这是一个日志操作类,他的核心功能是将日志记录按一定的逻辑记录下来,但是不可避免,你需要有元数据清理功能,你需要一些类支持将我们的数据写到对应的存储上,你还需要校验一些校验码,甚至做一个checkpoint,这些逻辑都需要访问到DeltaLog主类的一些方法和变量(比如logPath,dataPath等等),而DeltaLog主类也需要访问到这些额外功能里的方法和变量。我们知道继承只能满足单向”可见“,也就是deltaLog可以看到如MetadataCleanUp的所有方法和变量,反之MetadataCleanUp 则看不到deltaLog的变量和方法。Scala 通过一个神奇的语法让这个变得可能:

代码语言:javascript
复制
trait MetadataCleanup {
  self: DeltaLog =>

这里,我们在trait的第一行,添加了self:DetaLog => ,表示MetadataCleanup其实就是一个DeltaLog实例的一部分。现在,在MetadataCleanUp里就可以访问DeltaLog里的变量和方法了:

代码语言:javascript
复制
trait MetadataCleanup {
  self: DeltaLog =>

   def doLogCleanup(): Unit = {
     delelte(logPath)//访问logPath变量 
  }
}

Scala的这种模式,可以很好的将一个大类拆分成N个小类(trait),并且还非常好的解决了他们之间的双向可见性。非常优秀的设计。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档