Skip to content

Instantly share code, notes, and snippets.

@stefanoc
Last active August 29, 2015 14:04
Show Gist options
  • Save stefanoc/b4b7c51b5c2524ffc576 to your computer and use it in GitHub Desktop.
Save stefanoc/b4b7c51b5c2524ffc576 to your computer and use it in GitHub Desktop.
Protocol implementation in Ruby
class Protocol
attr_reader :name, :ancestors
def initialize(name, ancestors, block)
@name = name
@ancestors = ancestors
@methods = []
instance_exec(&block)
end
def method(name)
@methods << name
end
def check_type!(type, _proto = self)
@ancestors.all? { |protocol| protocol.check_type!(type, _proto) }
@methods.each do |name|
type.instance_methods.include?(name) or raise "Incomplete protocol '#{_proto.name}' implementation (missing methd '#{name}')"
end
end
def check_object!(object)
object.class._protos.include?(name) or raise "#{object} does not conform to protocol '#{name}'"
end
alias_method :&, :check_object!
end
def protocol(name, *ancestors, &block)
Protocol.new(name, ancestors, block).tap do |proto|
Object.const_set(name, proto)
end
end
module ProtocolSupport
def _protos
@_protos ||= {}
end
def implements(protocol)
protocol.check_type!(self)
_protos[protocol.name] = true
end
end
Class.send :include, ProtocolSupport
#------------------------------------------------------------
protocol :FullyNamed do
method :full_name
end
protocol :Addressable, (FullyNamed) do
method :street_address
method :city
end
Person = Struct.new(:first_name, :last_name)
class Person
def full_name
[first_name, last_name].join ' '
end
end
DeliveryAddress = Struct.new(:person, :street_address, :city)
class DeliveryAddress
def full_name
person.full_name
end
end
class Pet
def initialize(name)
@name = name
end
end
def print_full_name(o)
FullyNamed & o
puts o.full_name
end
def print_full_address(o)
Addressable & o
puts o.full_name
puts o.street_address
puts o.city
end
Person.implements FullyNamed
DeliveryAddress.implements Addressable
print_full_name Person.new('John', 'Smith')
puts '---'
print_full_address DeliveryAddress.new(Person.new('Jane', 'Doe'), 'Via Larga 1', 'Milano')
puts '---'
print_full_name Pet.new('Cuddles')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment