C++的 Model 类实现

在进行 app 开发的时候,我们经常需要使用数据库,当然一开始都是最原始 SQL 语句的使用(略写 SELECT ..),但是这样使用实际上很不方便,所以后来渐渐的演变出了 ORM,就是通过 OOP 把数据库操作模型化,把每种 table 都抽象成一个 Model(模型),简单的说,就是可以完成如下的操作:

User user = User();
user.setName("Lianchengjiu");
user.setAge(19);
user.save(); // 这条记录就会被保存到数据库

//另外,还应该支持这样的操作

int userNumber = User::size(); //获取用户个数
User anotherUser = User::findById(1); //通过ID来查找User
User userLcj = User::findByName("LCJ"); //通过name字段来查找User

模型化之后,实现也不是很难,大概结构如下:

//User.h

class User {
public:
	void setName(string name);
    void setAge(int age);
    save();
    
    static int size();
    static User findById(int id);
    static User findByName(string name);
    static string tableName;
private:
	string name;
    int age;
    int id;
}

实现起来相当简单,但是有2个缺陷

  1. 如果字段扩充了,必须添加setter方法,getter方法,与此同时save()方法也必须加上这个字段。而在开发中,加入一个新的字段,其实是非常普遍的。

  2. 如果新建一个模型,同样需要新建一个类,但是这个类的大部分方法,其实和User类都是可以共用的!

写程序的一个原则就是,代码能重用就可以重用,所以我们很自然的可以想出,这2个缺陷,可以通过继承来实现。我们需要定义出一个通用的Model类,大概看上去是下面这个样子:

class Model {
public:
	static string tableName; //保存表名
    static map<string, string> fields; //通过一个hash表来保存字段,这样就可以实现字段的动态扩展
    
    void save(); //通用的保存方法
    void set(string fieldName, string fieldValue); //setter 方法
    string get(string fieldName) // getter 方法
    
    static int size();
    static Model findById(int id);
    
private:
	map<string, string> data; // 通过一个hash表来保存每个实例的数据,为了简化问题,我们假设所有的数据都是string
}

这样,我们就可以新建一个类去继承Model类,就可以获得这些方法,然后就可以完成任务啦!但是!!继承有个很重要的问题,那就是静态成员不能被覆盖,不能被重载,也就是说,每一个派生类,都不能拥有自己的tableName...

幸好,我们有一个魔法可以解决这个问题,那就是CRTP(奇异递归模板模式,很诡异的名字),大概的意思,就是让派生的User类去继承以User类为参数的Model模板...看代码吧

// 这一部分代码和之前的差不多,只是把Model写成了一个模板类
template <class T>
class Model {
public:
	static string tableName; //保存表名
    static map<string, string> fields; //通过一个hash表来保存字段,这样就可以实现字段的动态扩展
    
    void save(); //通用的保存方法
    void set(string fieldName, string fieldValue); //setter 方法
    string get(string fieldName) // getter 方法
    
    static int size();
    static T findById(int id);
    
private:
	map<string, string> data; // 通过一个hash表来保存每个实例的数据,为了简化问题,我们假设所有的数据都是string
};

// 重点!!!

class User : public Model<User> {
public:
	void setName(string name); //调用父类的set方法进行封装
};

template <> Model<User>::tableName = "Users";

class Boos : public Model<Book> {
};

template <> Model<Book>::tableName = "Books";

至此,我们就用很奇妙的方法解决了这个问题....由于User类继承的是Model类,而Model类和Model类是2个完全独立的类,所以其存的静态成员也是不同的!而User类自己只需要去封装父类的set和get方法就可以完美的使用啦!!!

可以参考的代码:https://github.com/lcjnil/WordsGame-v1/blob/version3/server/src/model/Model.h
https://github.com/lcjnil/WordsGame-v1/blob/version3/server/src/model/User.h

Table of Contents