< 返回版块

TideDlrow 发表于 2021-01-20 14:36

Tags:面向对象,

(图片不知道为啥用base64的显示不了 直接显示的字符串) 直接用文字说明吧。

中国象棋:

首先是一个棋盘Board,里面存放了所有的棋子;

抽象类Piece,里面又棋子的当前位置x和y,还有一个verify方法;

棋子类都继承了Piece,有Assistants,Bishop,Cannon,King,Knight,Pawn,Rook

用java实现的话如下:

public class Board{
    private Piece[][] board = new Piece[10][9];
    public Piece getPieceByCoordinate(int x, int y) {
		//some code..
    }
}
public abstract class Piece{
    private int x;
    private int y;
    private boolean camp;
    //判断棋子能否移动到nextX,nextY,需要当前局面,故要一个Board参数
    public abstract boolean verify(int nextX, int nextY, Board board);
}

public class  Assistants extends Piece{
    @Override
    public boolean verify(int nextX, int nextY, Board board) {
        //some code...
    }
}
...

但是在Rust上要实现上面的功能却颇为困难(对我来说)。我的大致实现如下

///这个用来代替Piece抽象类
trait Verify{
    fn verify(next_x: i32, next_y: i32, board:Board<dyn Verify>) -> bool;
}
///棋盘
pub struct Board<T: Verify> {
    //为了测试方便,先用2行1列试试
    board: [[Box<T>; 1]; 2],
}
impl<T: Verify + Default+Debug> Board<T> {
    pub fn new(pieces: [[Box<T>; 1]; 2]) -> Board<T> {
        Board {
            board: pieces
        }
    }
}
///车
#[derive(Default, Debug)]
pub struct Rook {
    pub x: i32
}

impl Verify for Rook {
    fn verify(next_x: i32, next_y: i32, board:Board<dyn Verify>) -> bool {
        false
    }
}
///象
#[derive(Default, Debug)]
pub struct Bishop{
    pub x: i32
}
impl Verify for Bishop {
    fn verify(next_x: i32, next_y: i32, board:Board<dyn Verify>) -> bool {
        false
    }
}

#[test]
fn test() {
    let pieces:[[Box<dyn Verify>; 1]; 2] = [
        // [Box::new(Bishop::default())],
        [Box::new(Rook::default())],
        [Box::new(Rook::default())],

    ];
    let board = Board::new(pieces);
    // println!("{:?}", board);
}

遇到了一堆问题,有

fn verify(next_x: i32, next_y: i32, board:Board<dyn Verify>) -> bool;
  |                                               ^^^^^^^^^^^^^^^^^ `board::Verify` cannot be made into an object

还有就是测试代码中

//我的本意是只要是实现了Verify的都可以放到这个二维数组中
let pieces:[[Box<dyn Verify>; 1]; 2] = [
        // [Box::new(Bishop::default())],
        [Box::new(Rook::default())],
        [Box::new(Rook::default())],
    ];
//但是这里的报错和上面差不多
let pieces:[[Box<dyn Verify>; 1]; 2] = [
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^ `board::Verify` cannot be made into an object
    
//如果我不写类型推断是可以编译通过的,但是里面只能放一种类型的数据 要么是Rook要么是Bishop
    let pieces = [
        // [Box::new(Bishop::default())],
        [Box::new(Rook::default())],
        [Box::new(Rook::default())],
    ];

所以想请问一下大家,该怎么解决这些问题呢?

评论区

写评论
eweca 2021-01-21 14:39

个人认为我这种方法也是实现了多态性呀,虽然实现方式不JAVA就是了。这里统一了接口verify,但是根据传入的枚举类型不同,采用了不同的处理方法。这不就是多态的哲学么:对于所有继承了同一超类(Piece)的类(rook等)生成的对象(rook(x,y)),采用了了同一个接口接受,使用不同方法处理。

不过我能理解你,每个人都有自己用顺手的写法,而且你这方法可能更通用些。我就随口嘴一句,哈哈。

--
👇
TideDlrow: 这确实是一种很好的解决方案,感谢。 不过我主要是想用Rust试试多态性

--
👇
eweca: 我没学过java也不是学CS的,我自己来写的话我会这么写:

其实完全用不上泛型或者trait什么的,太麻烦了,棋子的数量种类是有限的,直接枚举就好了啊。

pub struct Board {
    board: [[Piece; 1]; 2],
}

impl Board {
    pub fn new(pieces: [[Piece; 1]; 2]) -> Board {
        Board {board: pieces}
    }
}

pub enum Piece {
    Rook(i32, i32),
    Bishop(i32, i32),
}

impl Piece {
    fn verify(piece: Piece, next_x: i32, next_y: i32, board: &Board) -> bool {
        match piece {
            Piece::Rook(x, y) => { Self::rook(x, y, next_x, next_y, board) },
            Piece::Bishop(x, y) => { Self::bishop(x, y, next_x, next_y, board) },
        }
    }

    fn rook(x: i32, y: i32, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }

    fn bishop(x: i32, y: i32, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }
}

--
👇
TideDlrow: 一开始我是完全按照java中的来,但是编译时报不能确定大小,我就包了层Box,然后ide就在泛型那边建议加个dyn。最后就成了上面的样子。

作者 TideDlrow 2021-01-21 12:46

感谢!!这就是我想要的ψ(`∇´)ψ

--
👇
Aya0wind:

///这个用来代替Piece抽象类
pub trait Verify: Debug {
    fn verify(&self, next_x: i32, next_y: i32, board: &Board) -> bool;
}
///棋盘
#[derive(Debug)]
pub struct Board {
    //为了测试方便,先用2行1列试试
    board: [[Box<dyn Verify>; 1]; 2],
}
impl Board {
    pub fn new(pieces: [[Box<dyn Verify>; 1]; 2]) -> Board {
        Board { board: pieces }
    }
}
///车
#[derive(Default, Debug)]
pub struct Rook {
    pub x: i32,
}

impl Verify for Rook {
    fn verify(&self, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }
}
///象
#[derive(Default, Debug)]
pub struct Bishop {
    pub x: i32,
}
impl Verify for Bishop {
    fn verify(&self, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }
}

#[test]
fn test() {
    let pieces: [[Box<dyn Verify>; 1]; 2] = [
        [Box::new(Rook::default())],
        [Box::new(Bishop::default())],
    ];
    let board = Board::new(pieces);
    board.board.iter().for_each(|x| {
        x[0].verify(1, 1, &board);
    });
}

rust里引用类型要用&Type,单独一个Type是获得所有权。你的verify只是读取棋盘,并不是棋子获得棋盘的所有权,所以要加&。然后就是trait要动态分发必须有self参数,没有self的函数就像java里的static方法一样是无法多态调用的,self就像java里的this一样,只不过java是成员方法默认有个this,rust要手动加。最后你的Board类型不需要用泛型,直接在成员那用Box就行。

作者 TideDlrow 2021-01-21 12:44

这确实是一种很好的解决方案,感谢。 不过我主要是想用Rust试试多态性

--
👇
eweca: 我没学过java也不是学CS的,我自己来写的话我会这么写:

其实完全用不上泛型或者trait什么的,太麻烦了,棋子的数量种类是有限的,直接枚举就好了啊。

pub struct Board {
    board: [[Piece; 1]; 2],
}

impl Board {
    pub fn new(pieces: [[Piece; 1]; 2]) -> Board {
        Board {board: pieces}
    }
}

pub enum Piece {
    Rook(i32, i32),
    Bishop(i32, i32),
}

impl Piece {
    fn verify(piece: Piece, next_x: i32, next_y: i32, board: &Board) -> bool {
        match piece {
            Piece::Rook(x, y) => { Self::rook(x, y, next_x, next_y, board) },
            Piece::Bishop(x, y) => { Self::bishop(x, y, next_x, next_y, board) },
        }
    }

    fn rook(x: i32, y: i32, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }

    fn bishop(x: i32, y: i32, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }
}

--
👇
TideDlrow: 一开始我是完全按照java中的来,但是编译时报不能确定大小,我就包了层Box,然后ide就在泛型那边建议加个dyn。最后就成了上面的样子。

Aya0wind 2021-01-21 10:28
///这个用来代替Piece抽象类
pub trait Verify: Debug {
    fn verify(&self, next_x: i32, next_y: i32, board: &Board) -> bool;
}
///棋盘
#[derive(Debug)]
pub struct Board {
    //为了测试方便,先用2行1列试试
    board: [[Box<dyn Verify>; 1]; 2],
}
impl Board {
    pub fn new(pieces: [[Box<dyn Verify>; 1]; 2]) -> Board {
        Board { board: pieces }
    }
}
///车
#[derive(Default, Debug)]
pub struct Rook {
    pub x: i32,
}

impl Verify for Rook {
    fn verify(&self, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }
}
///象
#[derive(Default, Debug)]
pub struct Bishop {
    pub x: i32,
}
impl Verify for Bishop {
    fn verify(&self, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }
}

#[test]
fn test() {
    let pieces: [[Box<dyn Verify>; 1]; 2] = [
        [Box::new(Rook::default())],
        [Box::new(Bishop::default())],
    ];
    let board = Board::new(pieces);
    board.board.iter().for_each(|x| {
        x[0].verify(1, 1, &board);
    });
}

rust里引用类型要用&Type,单独一个Type是获得所有权。你的verify只是读取棋盘,并不是棋子获得棋盘的所有权,所以要加&。然后就是trait要动态分发必须有self参数,没有self的函数就像java里的static方法一样是无法多态调用的,self就像java里的this一样,只不过java是成员方法默认有个this,rust要手动加。最后你的Board类型不需要用泛型,直接在成员那用Box就行。

eweca 2021-01-20 16:26

调试回复了下,突然多了好多评论。正如楼下所说,Enum更方便呢。

eweca 2021-01-20 16:25

我没学过java也不是学CS的,我自己来写的话我会这么写:

其实完全用不上泛型或者trait什么的,太麻烦了,棋子的数量种类是有限的,直接枚举就好了啊。

pub struct Board {
    board: [[Piece; 1]; 2],
}

impl Board {
    pub fn new(pieces: [[Piece; 1]; 2]) -> Board {
        Board {board: pieces}
    }
}

pub enum Piece {
    Rook(i32, i32),
    Bishop(i32, i32),
}

impl Piece {
    fn verify(piece: Piece, next_x: i32, next_y: i32, board: &Board) -> bool {
        match piece {
            Piece::Rook(x, y) => { Self::rook(x, y, next_x, next_y, board) },
            Piece::Bishop(x, y) => { Self::bishop(x, y, next_x, next_y, board) },
        }
    }

    fn rook(x: i32, y: i32, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }

    fn bishop(x: i32, y: i32, next_x: i32, next_y: i32, board: &Board) -> bool {
        false
    }
}

--
👇
TideDlrow: 一开始我是完全按照java中的来,但是编译时报不能确定大小,我就包了层Box,然后ide就在泛型那边建议加个dyn。最后就成了上面的样子。

ezlearning 2021-01-20 16:18

参考这里:

https://doc.rust-lang.org/reference/items/traits.html#supertraits

ezlearning 2021-01-20 16:15

你可以这样定义trait:

pub trait Verify {
    fn verify(&self, next_x: i32, next_y: i32) -> bool;
}
PrivateRookie 2021-01-20 16:14
pub trait Verify {
    fn verify(&self, next_x: i32, next_y: i32) -> bool;
}

pub enum PiecesType {
    Rook,
    Bishop,
}

pub struct Pieces {
    pub ty: PiecesType,
    pub x: i32,
}

impl Verify for Pieces {
    fn verify(&self, next_x: i32, next_y: i32) -> bool {
        match self.ty {
            PiecesType::Rook => false,
            PiecesType::Bishop => false,
        }
    }
}

pub struct Board {
    pieces: [[Pieces; 1]; 2],
}

fn main() {}

#[test]
fn test() {
    let pieces: [[Pieces; 1]; 2] = [
        [Pieces {
            ty: PiecesType::Rook,
            x: 0,
        }],
        [Pieces {
            ty: PiecesType::Bishop,
            x: 1,
        }],
    ];
    let board = Board { pieces };
    // println!("{:?}", board);
}

感觉用 enum 会比 trait 好些点

作者 TideDlrow 2021-01-20 15:51

一开始我是完全按照java中的来,但是编译时报不能确定大小,我就包了层Box,然后ide就在泛型那边建议加个dyn。最后就成了上面的样子。

eweca 2021-01-20 15:29

个人理解,实际上你就是想要让Board里的棋子只有实现了verify的,这样你才能用verify判断那个棋子可以不可以移动到指定位置。但是实际上这个只要用到trait bound的功能就可以了。跟dyn这种动态派发的机制貌似没啥关系吧?

eweca 2021-01-20 15:26

业余玩家,说错勿喷。如果你只是想要struct board只接受实现了trait的泛型T,那么trait那边这么写吧:

pub trait Verify{
    fn verify<T: Verify>(next_x: i32, next_y: i32, board:Board<T>) -> bool;
}
yct21 2021-01-20 15:22

混淆了 generic 和 trait object,下面这段代码在编译期就实例化了的。

pub struct Board<T: Verify> {
    //为了测试方便,先用2行1列试试
    board: [[Box<T>; 1]; 2],
}

要动态分发的话,用 dyn Verify (你后面不都用到了么)

1 共 13 条评论, 1 页