Rustでの継承とコード多重化
4469 ワード
Rustを学習する過程で突然継承をどのように実現するか、特にコード多重化の継承に用いるかを考え、ネットで調べたところ、そんなに簡単ではないことが分かった.
C++の継承
まずc++の中でどのようにしているかを見てみましょう.
たとえば、シーンノードを作成するノードクラスとSpriteクラスが継承します.
nodeベースクラスを定義する
サブクラスSpriteを定義し、drawメソッドを再ロードします.
spriteはノードとして使用でき、ノードのmove_を再利用できます.to関数:
Rustでの継承
今はRustで同じことをします.ノードベースクラスを定義します.
サブクラスを定義するときに私たちはトラブルに遭遇しました:Rustのstructは継承できません!
このように書くと間違いを報告します.
virtual structって何ですか?Rustにはvirtual structの特性があり、structに別のstructを継承させることができたが、削除された:(RFCはここにある.現在Rustのstructは継承できない.
traitの使用
Rustのtraitはjavaのinterfaceに似ていて、継承できます.ノードをtraitと定義します.
しかし、ノードではmoveを実現できないことに気づきました.toメソッドは、traitにメンバーデータ:x,yが存在しないためです.
では、各サブクラスにそれぞれのメソッド実装を書くしかありません.たとえば、空のノードクラスとSpriteクラスが必要です.
大量のコードが重複していると思いますか?Spriteはdrawメソッドを書き換えるだけですが、すべてのメソッドを一度実現します.多くのノードを実現するには、それぞれを実現しなければならないなら、吐血を書く必要があります.
コンポジット
組み合わせはコード再利用の良い方法です.コードを再利用する場合、結合は継承よりも「has-a」の関係を表すことができます.ノードを以前のstructベースクラスとして再定義し、Spriteにノードを配置します.
さっぱりしていて、足りないのはmoveを省略できないことです.toメソッドは,手動で書き,ノード内の同名メソッドを簡単に呼び出す.
Spriteをノードに変換できないなど、組み合わせや継承にはいくつかの違いがあります.
Deref & DerefMut trait
std::ops::Derefは値演算子を再ロードするために使用されます:*.このリロードは他のタイプを返すことができ,組合せでタイプを変換できない問題をちょうど解決することができる.
この例ではmove_toのselfは可変なので、DerefとDerefMutを実現します
あとで&Spriteを&Nodeに変換できます
注意したいのはsprite_Nodeのメソッド呼び出しリロードは機能しません.sprite_node.draw()呼び出しはNode.Spriteではなくdraw()draw().
サブクラスのメソッドを呼び出す場合は、サブクラスタイプの変数が呼び出される必要があります.
C++の継承
まずc++の中でどのようにしているかを見てみましょう.
たとえば、シーンノードを作成するノードクラスとSpriteクラスが継承します.
nodeベースクラスを定義する
struct Node {
float x;
float y;
void move_to(float x, float y) {
this->x = x;
this->y = y;
}
virtual void draw() const {
printf("node: x = %f, y = %f
", x, y);
}
};
サブクラスSpriteを定義し、drawメソッドを再ロードします.
struct Sprite: public Node {
virtual void draw() const {
printf("sprite: x = %f, y = %f
", x, y);
}
};
spriteはノードとして使用でき、ノードのmove_を再利用できます.to関数:
Node* sprite = new Sprite();
sprite->move_to(10, 10);
sprite->draw();
Rustでの継承
今はRustで同じことをします.ノードベースクラスを定義します.
struct Node {
x: f32,
y: f32,
}
impl Node {
fn draw(&self) {
println!("node: x={}, y={}", self.x, self.y)
}
fn move_to(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
}
}
サブクラスを定義するときに私たちはトラブルに遭遇しました:Rustのstructは継承できません!
struct Sprite: Node;
このように書くと間違いを報告します.
error: `virtual` structs have been removed from the language
virtual structって何ですか?Rustにはvirtual structの特性があり、structに別のstructを継承させることができたが、削除された:(RFCはここにある.現在Rustのstructは継承できない.
traitの使用
Rustのtraitはjavaのinterfaceに似ていて、継承できます.ノードをtraitと定義します.
trait Node {
fn move_to(&mut self, x: f32, y: f32);
fn draw(&self);
}
しかし、ノードではmoveを実現できないことに気づきました.toメソッドは、traitにメンバーデータ:x,yが存在しないためです.
では、各サブクラスにそれぞれのメソッド実装を書くしかありません.たとえば、空のノードクラスとSpriteクラスが必要です.
struct EmptyNode {
x: f32,
y: f32,
}
impl Node for EmptyNode {
fn draw(&self) {
println!("node: x={}, y={}", self.x, self.y)
}
fn move_to(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
}
}
struct Sprite {
x: f32,
y: f32,
}
impl Node for Sprite {
fn draw(&self) {
println!("sprite: x={}, y={}", self.x, self.y)
}
fn move_to(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
}
}
大量のコードが重複していると思いますか?Spriteはdrawメソッドを書き換えるだけですが、すべてのメソッドを一度実現します.多くのノードを実現するには、それぞれを実現しなければならないなら、吐血を書く必要があります.
コンポジット
組み合わせはコード再利用の良い方法です.コードを再利用する場合、結合は継承よりも「has-a」の関係を表すことができます.ノードを以前のstructベースクラスとして再定義し、Spriteにノードを配置します.
struct Node {
x: f32,
y: f32,
}
impl Node {
fn draw(&self) {
println!("node: x={}, y={}", self.x, self.y)
}
fn move_to(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
}
}
struct Sprite {
node: Node
}
impl Sprite {
fn draw(&self) {
println!("sprite: x={}, y={}", self.node.x, self.node.y)
}
fn move_to(&mut self, x: f32, y: f32) {
self.node.move_to(x, y);
}
}
さっぱりしていて、足りないのはmoveを省略できないことです.toメソッドは,手動で書き,ノード内の同名メソッドを簡単に呼び出す.
Spriteをノードに変換できないなど、組み合わせや継承にはいくつかの違いがあります.
Deref & DerefMut trait
std::ops::Derefは値演算子を再ロードするために使用されます:*.このリロードは他のタイプを返すことができ,組合せでタイプを変換できない問題をちょうど解決することができる.
この例ではmove_toのselfは可変なので、DerefとDerefMutを実現します
struct Sprite {
node: Node
}
impl Sprite {
fn draw(&self) {
println!("sprite: x={}, y={}", self.node.x, self.node.y)
}
}
impl Deref for Sprite {
type Target = Node;
fn deref<'a>(&'a self) -> &'a Node {
&self.node
}
}
impl DerefMut for Sprite {
fn deref_mut<'a>(&'a mut self) -> &'a mut Node {
&mut self.node
}
}
あとで&Spriteを&Nodeに変換できます
let mut sprite = Sprite{ node: Node { x: 10.0, y: 20.0 } };
let mut sprite_node: &mut Node = &mut sprite;
sprite_node.move_to(100.0, 100.0);
注意したいのはsprite_Nodeのメソッド呼び出しリロードは機能しません.sprite_node.draw()呼び出しはNode.Spriteではなくdraw()draw().
サブクラスのメソッドを呼び出す場合は、サブクラスタイプの変数が呼び出される必要があります.
let mut sprite = Sprite{ node: Node { x: 10.0, y: 20.0 } };
// mut borrow
{
let mut sprite_node: &mut Node = &mut sprite;
sprite_node.move_to(100.0, 100.0);
sprite.node.draw(); // node: x=100, y=100
}
sprite.draw(); // sprite: x=100, y=100