Rust
オブジェクト指向の錆
サーチ…
前書き
Rustは、その代数的データ型が関連するメソッドを持つことができるという点でオブジェクト指向であり、そのデータを操作する方法を知っているコードと共に格納されたデータの意味でのオブジェクトにします。
しかし、Rustは遺伝を支持しておらず、形質との関係を支持していません。これは、多くのOOパターンがそのまま動作せず、変更する必要があることを意味します。いくつかは完全に無関係です。
形質の継承
Rustには、構造体のプロパティを "継承する"という概念はありません。その代わりに、オブジェクト間の関係を設計するときには、インタフェース(Rustの特性 )によって機能が定義されるようにします。これにより継承を超えた構成が促進されます。これは、より大規模なプロジェクトにもっと有用で容易に拡張できると考えられています。
Pythonでいくつかの継承例を使用した例を次に示します。
class Animal:
def speak(self):
print("The " + self.animal_type + " said " + self.noise)
class Dog(Animal):
def __init__(self):
self.animal_type = 'dog'
self.noise = 'woof'
これをRustに翻訳するには、動物を構成するものを取り出し、その機能を形質に取り入れる必要があります。
trait Speaks {
fn speak(&self);
fn noise(&self) -> &str;
}
trait Animal {
fn animal_type(&self) -> &str;
}
struct Dog {}
impl Animal for Dog {
fn animal_type(&self) -> &str {
"dog"
}
}
impl Speaks for Dog {
fn speak(&self) {
println!("The dog said {}", self.noise());
}
fn noise(&self) -> &str {
"woof"
}
}
fn main() {
let dog = Dog {};
dog.speak();
}
その抽象的な親クラスを2つの別々のコンポーネント、つまり構造体を動物として定義する部分と、それが話すことを可能にする部分に分割した方法に注意してください。
巧妙な読者は、すべての実装者が論理を再実装して "{動物}が{ノイズ}"という形で文字列を印刷しなければならないので、これが1対1ではないことに気付くだろう。 Speak
for Animal
を実装しているインタフェースを少し変更してこれを行うことができます:
trait Speaks {
fn speak(&self);
}
trait Animal {
fn animal_type(&self) -> &str;
fn noise(&self) -> &str;
}
impl<T> Speaks for T where T: Animal {
fn speak(&self) {
println!("The {} said {}", self.animal_type(), self.noise());
}
}
struct Dog {}
struct Cat {}
impl Animal for Dog {
fn animal_type(&self) -> &str {
"dog"
}
fn noise(&self) -> &str {
"woof"
}
}
impl Animal for Cat {
fn animal_type(&self) -> &str {
"cat"
}
fn noise(&self) -> &str {
"meow"
}
}
fn main() {
let dog = Dog {};
let cat = Cat {};
dog.speak();
cat.speak();
}
動物が騒音を発し、話すだけで、今は動物であるもののための実装が行われていることに注目してください。これは、以前のやり方とPython継承のどちらよりもはるかに柔軟です。たとえば、異なる音を持つHuman
を追加したい場合、代わりに、何かのためにspeak
別の実装を持つことができますHuman
:
trait Human {
fn name(&self) -> &str;
fn sentence(&self) -> &str;
}
struct Person {}
impl<T> Speaks for T where T: Human {
fn speak(&self) {
println!("{} said {}", self.name(), self.sentence());
}
}
ビジターパターン
Javaの典型的なVisitorの例は次のとおりです。
interface ShapeVisitor {
void visit(Circle c);
void visit(Rectangle r);
}
interface Shape {
void accept(ShapeVisitor sv);
}
class Circle implements Shape {
private Point center;
private double radius;
public Circle(Point center, double radius) {
this.center = center;
this.radius = radius;
}
public Point getCenter() { return center; }
public double getRadius() { return radius; }
@Override
public void accept(ShapeVisitor sv) {
sv.visit(this);
}
}
class Rectangle implements Shape {
private Point lowerLeftCorner;
private Point upperRightCorner;
public Rectangle(Point lowerLeftCorner, Point upperRightCorner) {
this.lowerLeftCorner = lowerLeftCorner;
this.upperRightCorner = upperRightCorner;
}
public double length() { ... }
public double width() { ... }
@Override
public void accept(ShapeVisitor sv) {
sv.visit(this);
}
}
class AreaCalculator implements ShapeVisitor {
private double area = 0.0;
public double getArea() { return area; }
public void visit(Circle c) {
area = Math.PI * c.radius() * c.radius();
}
public void visit(Rectangle r) {
area = r.length() * r.width();
}
}
double computeArea(Shape s) {
AreaCalculator ac = new AreaCalculator();
s.accept(ac);
return ac.getArea();
}
これは簡単に2つの方法でRustに変換できます。
最初の方法は、実行時の多態性を使用します。
trait ShapeVisitor {
fn visit_circle(&mut self, c: &Circle);
fn visit_rectangle(&mut self, r: &Rectangle);
}
trait Shape {
fn accept(&self, sv: &mut ShapeVisitor);
}
struct Circle {
center: Point,
radius: f64,
}
struct Rectangle {
lowerLeftCorner: Point,
upperRightCorner: Point,
}
impl Shape for Circle {
fn accept(&self, sv: &mut ShapeVisitor) {
sv.visit_circle(self);
}
}
impl Rectangle {
fn length() -> double { ... }
fn width() -> double { ... }
}
impl Shape for Rectangle {
fn accept(&self, sv: &mut ShapeVisitor) {
sv.visit_rectangle(self);
}
}
fn computeArea(s: &Shape) -> f64 {
struct AreaCalculator {
area: f64,
}
impl ShapeVisitor for AreaCalculator {
fn visit_circle(&mut self, c: &Circle) {
self.area = std::f64::consts::PI * c.radius * c.radius;
}
fn visit_rectangle(&mut self, r: &Rectangle) {
self.area = r.length() * r.width();
}
}
let mut ac = AreaCalculator { area: 0.0 };
s.accept(&mut ac);
ac.area
}
2番目の方法はコンパイル時の多態性を代わりに使用しますが、違いはここに示します。
trait Shape {
fn accept<V: ShapeVisitor>(&self, sv: &mut V);
}
impl Shape for Circle {
fn accept<V: ShapeVisitor>(&self, sv: &mut V) {
// same body
}
}
impl Shape for Rectangle {
fn accept<V: ShapeVisitor>(&self, sv: &mut V) {
// same body
}
}
fn computeArea<S: Shape>(s: &S) -> f64 {
// same body
}