Mixins
Objective: Create methods that can be injected into classes of our choice. Methods should have access to the fields of the instances. Injection should not involve inheritance.
Python
Python supports multiple-inheritance. It is natural to use it for this use case:
class Greeting:
def hello(self):
print(f"Hello, my name is {self.name}. How can I help you?")
class Farewell:
def bye(self):
print("Thank you. Bye!")
class ChatBot(Greeting, Farewell):
def __init__(self, name):
self.name = name
bot = ChatBot("R2D2")
bot.hello()
bot.bye()
Using inheritance hovewer violates definition of a mixin. If this is important for the design, we can copy methods without affecting MRO:
def mixin(*sources):
def decorator(target):
for src in sources:
for key in dir(src):
if not (key.startswith('__') and key.endswith('__')):
setattr(target, key, getattr(src, key))
return target
return decorator
class Greeting:
def hello(self):
print(f"Hello, my name is {self.name}. How can I help you?")
class Farewell:
def bye(self):
print("Thank you. Bye!")
@mixin(Greeting, Farewell)
class ChatBot:
def __init__(self, name):
self.name = name
bot = ChatBot("R2D2")
bot.hello()
bot.bye()
More sophisticated packages like mixin can be used.
Rust
Rust doesn't support inheritance. Traits are natural way of fulfilling objectives.
Note that associated functions can't directly access fields of the structure. For this reason we define helper trait Named
.
trait Named { fn name(&self) -> &str; } trait Greeting : Named { fn hello(&self) { println!("Hello, my name is {}. How can I help you?", self.name()); } } trait Farewell { fn bye(&self) { println!("Thank you. Bye!"); } } struct ChatBot { name : String } impl Greeting for ChatBot {} impl Farewell for ChatBot {} impl Named for ChatBot { fn name(&self) -> &str { &self.name } } fn main() { let bot = ChatBot{name: "R2D2".to_string()}; bot.hello(); bot.bye(); }
Crystal
Crystal supports single-inheritance. Mixins can be implemented using modules. Methods in the module can directly access fields of the structure. This is checked at compile time.
module Greeting
def hello
puts "Hello, my name is #{@name}. How can I help you?"
end
end
module Farewell
def bye
puts "Thank you. Bye!"
end
end
class ChatBot
include Greeting
include Farewell
def initialize(@name : String)
end
end
trx = ChatBot.new "R2D2"
trx.hello
trx.bye