通常我們會希望繼承於 Base class 底下的 class 會有相同的 interface,for example, 每個 service 都要有一個 #call 的 instance method,應該要有個很簡單的方法來對這些 services 測試是否有 implement 這個 protocal,理想情況如下:
```rb
class BaseService
def call
raise "NotImplemented"
end
end
class FooService < BaseService
def call
puts "called"
end
end
class BarService < BaseService
end
module Callable
def call
puts "called from module"
end
end
class IncludeService
include Callable
end
class PrivateService
private
def call
puts "private call"
end
end
RSpec.describe FooService do
describe "interfaces" do
specify do
expect(FooService).to has_interface :call
expect(FooService).to has_implemented_interface :call
end
end
end
```
it_has_interface => 有 respond_to 這個 method 就行 (個人覺得沒什麼用...)
it_has_implemented_interface => 有自己 implement (排除母 class) 或是 include method 進來
it_has_implemented_interface 希望達到的效果是:
FooService 和 IncludeService 會 passed
BarService 和 PrivateService 會 failed
來看看要怎麼樣做 :D
已知使用 respond_to? 是不行的,因為母 class 定義了 call,所以
```ruby
2.4.4 :013 > f = FooService.new
=> #<FooService:0x00007fed06152098>
2.4.4 :014 > b = BarService.new
=> #<BarService:0x00007fed06148700>
2.4.4 :015 > f.respond_to?(:call)
=> true
2.4.4 :016 > b.respond_to?(:call)
=> true
```
用 method_defined? 當然也不行,因為都是繼承於 BaseService 所以都有 defined,但來試來試一下
```ruby
2.4.4 :017 > FooService.method_defined?(:call)
=> true
2.4.4 :018 > BarService.method_defined?(:call)
=> true
```
我們要找的是一個方法檢測子 class 是否有定義這個 method,不管是不是用 mixin 的方式加進去的,有幾個 methods 可以用
* public_methods
* instance_methods
* public_instance_methods
instance_methods default 還會包含 protected 的 methods,不如我們先來試試看 public_instance_methods
```ruby
2.4.4 :030 > FooService.public_instance_methods.include?(:call)
=> true
2.4.4 :031 > BarService.public_instance_methods.include?(:call)
=> true
2.4.4 :032 > IncludeService.public_instance_methods.include?(:call)
=> true
2.4.4 :033 > PrivateService.public_instance_methods.include?(:call)
=> false
```
看起來還是不行,讓我們換個方法,用 public_methods(false)
```ruby
2.4.4 :068 > FooService.new.public_methods(false).include?(:call)
=> true
2.4.4 :069 > BarService.new.public_methods(false).include?(:call)
=> false
2.4.4 :070 > IncludeService.new.public_methods(false).include?(:call)
=> false
2.4.4 :071 > PrivateService.new.public_methods(false).include?(:call)
=> false
```
結果 public_methods(false) 沒辦法讓 include 進來的 method 包含在裡面,為什麼呢?可以看一下 IncludeService 的 ancessor
```
2.4.4 :072 > IncludeService.ancestors
=> [IncludeService, Callable, Object, Kernel, BasicObject]
```
可以想像成 include 其實就是在 parent class 以及 child class 中間插隊,所以也是算在 ancestors 裡面。
所以看起來沒有任何 built-in 的 method 可以直接做到這個功能,那不如我們就自幹一個看看吧?XD
由於要 support module 裡的 methods 所以想到最原始的方法就是去找 ancestors 裡在 superclass 之下的每個 instance_methods, code example:
```ruby
class Utils
class << self
def get_public_instance_methods_without_parent_class(klass)
countable_ancestors = klass.ancestors[0...klass.ancestors.index(klass.superclass)]
countable_ancestors.flat_map do |ancestor_class|
ancestor_class.public_instance_methods(false)
end.uniq
end
end
end
```
這個版本目前只能對instance method 做檢測,不過對我們來說已經足夠了,以後再想辦法擴充,接下來就是來研究自定義 rspec DSL 的部分。