【架构师基本功】贫血/充血模型

1. 贫血模型

贫血模型也叫做数据驱动模型,是一种面向对象设计的模式,其中业务逻辑主要由数据对象完成。在此模型中,数据对象包含数据和对数据的操作,而数据访问和业务逻辑则通常被放置在另一个对象中,如DAO(Data Access Object)或Service中。

1. 贫血模型的概念

贫血模型是一种面向对象设计的模式,其中业务逻辑由数据对象完成。贫血模型也被称为数据驱动模型。

2. 数据对象的作用

贫血模型指的是将数据和操作分离,在数据对象中只包含数据,而操作则全部放在业务逻辑层中实现。这种模型适用于简单的业务场景,但是不利于扩展和维护。

数据对象的作用是存储数据,实现对数据的操作,并在业务逻辑层中实现业务逻辑。因此,数据对象的设计应该包括以下内容:

  • 数据属性的定义
  • 操作方法的定义,包括增删改查等
  • 业务逻辑的实现,在操作方法中实现

Java代码示例:

public class Student {
    // 数据属性的定义
    private int id;
    private String name;
    private int age;

    // 操作方法的定义,包括增删改查等
    public void add() {
        // 实现添加操作
    }

    public void delete() {
        // 实现删除操作
    }

    public void update() {
        // 实现更新操作
    }

    public void search(int id) {
        // 实现查询操作
    }

    // 业务逻辑的实现,在操作方法中实现
    public void addStudent(Student student) {
        // 根据业务逻辑实现添加学生的操作
    }

    // 可以添加其他业务逻辑方法
}

3. 数据访问和业务逻辑的作用

在贫血模型中,数据访问和业务逻辑通常被放置在另一个对象中,如DAO(Data Access Object)或Service中。它们的作用是:

  • 数据访问层实现对数据的持久化操作,如数据的存储、读取等
  • 业务逻辑层实现业务逻辑,如数据的处理和计算等

以下是一个简单的Java代码示例,其中包括DAO和Service类,实现了对用户信息的持久化操作和业务逻辑处理:

  1. UserDao.java
public class UserDao {
    // 数据库连接等相关代码省略

    // 根据用户ID查询用户信息
    public User getUserById(int id) {
        // 实现具体的查询逻辑
        return user;
    }

    // 添加用户信息
    public void addUser(User user) {
        // 实现具体的插入操作
    }

    // 更新用户信息
    public void updateUser(User user) {
        // 实现具体的更新操作
    }

    // 根据用户ID删除用户
    public void deleteUserById(int id) {
        // 实现具体的删除操作
    }
}
  1. UserService.java
public class UserService {
    private UserDao userDao; // 通过依赖注入方式获取UserDao对象

    // 创建用户
    public void createUser(User user) {
        // 实现具体的业务逻辑,如检查用户信息等
        userDao.addUser(user);
    }

    // 修改用户信息
    public void updateUser(User user) {
        userDao.updateUser(user);
    }

    // 获取用户信息
    public User getUser(int id) {
        return userDao.getUserById(id);
    }

    // 删除用户
    public void deleteUser(int id) {
        userDao.deleteUserById(id);
    }
}

在上述代码中,UserDao实现了对用户信息的持久化操作,而UserService则实现了业务逻辑层的功能。通过将数据访问和业务逻辑分离,可以提高代码的可维护性和可扩展性。

4. DAO和Service的作用

在贫血模型中,DAO(Data Access Object)和Service是两个重要的对象。它们的作用是:

  • DAO:负责数据的存储和读取
  • Service:负责业务逻辑的处理和计算

下面是一个简单的Java代码示例,演示了如何创建DAO和Service类来实现贫血模型:

DAO类:

public class UserDAO {
    // 数据库连接等相关操作,这里省略

    public void save(User user) {
        // 将User对象保存到数据库中
    }

    public User getById(int id) {
        // 根据id从数据库中读取User对象并返回
    }
}

Service类:

public class UserService {
    private UserDAO userDao;

    public UserService() {
        this.userDao = new UserDAO();
    }

    public void saveUser(User user) {
        userDao.save(user);
    }

    public User getUserById(int id) {
        return userDao.getById(id);
    }

    public boolean isAdult(User user) {
        int age = user.getAge();
        // 对于“成年人”这个业务逻辑的判断,可以在Service层实现
        return age >= 18;
    }
}

在上面的示例中,UserDAO负责数据的存储和读取,UserService负责业务逻辑的处理和计算。这样做的好处是,使得代码结构更加清晰,提高了代码的可维护性和可扩展性。同时,由于业务逻辑和数据操作分离,也方便进行单元测试和集成测试。

5. 实现步骤

实现贫血模型的步骤如下:

  • 定义数据对象,包括数据和对数据的操作
  • 定义DAO对象,实现对数据的持久化操作
  • 定义Service对象,实现业务逻辑的处理和计算
  • 在业务逻辑中调用DAO和数据对象,完成数据的持久化操作和业务逻辑的处理

以下是Java代码示例,注释中包含了实现贫血模型的步骤:

// 定义数据对象,包括数据和对数据的操作
public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    // 操作数据的方法
    public void printUser() {
        System.out.println("name: " + name + ", age: " + age);
    }
}

// 定义DAO对象,实现对数据的持久化操作
public class UserDao {
    // 模拟数据库
    private List<User> userList = new ArrayList<>();

    // 持久化操作
    public void saveUser(User user) {
        userList.add(user);
    }

    public List<User> getUsers() {
        return userList;
    }
}

// 定义Service对象,实现业务逻辑的处理和计算
public class UserService {
    private UserDao userDao = new UserDao();

    // 业务逻辑的处理和计算
    public void addUser(User user) {
        userDao.saveUser(user);
    }

    public List<User> getUsers() {
        return userDao.getUsers();
    }
}

// 在业务逻辑中调用DAO和数据对象,完成数据的持久化操作和业务逻辑的处理
public class Test {
    public static void main(String[] args) {
        UserService userService = new UserService();

        User user1 = new User("Tom", 25);
        User user2 = new User("Jack", 30);

        userService.addUser(user1);
        userService.addUser(user2);

        List<User> userList = userService.getUsers();
        for (User user : userList) {
            user.printUser();
        }
    }
}

6. 实现效果

使用贫血模型可以实现数据和业务逻辑的分离,有利于代码的维护和开发。同时,也有利于代码的重用和扩展。

这种模型因其简单易懂、易于实现而受到广泛使用。其核心理念是将应用程序的逻辑和数据分离,数据对象仅包含属性和方法,将数据和表现分离,这样可以使实体对象具有最小的逻辑,代码重用性和可维护性都得到提高。

贫血模型的优点

1)逻辑分离,代码可维护性和可重用性高。

2)数据库操作封装,业务逻辑更加简化。

3)适合小型项目和简单业务的应用,开发效率高。

贫血模型的缺点

1)缺乏设计思路,较难支持复杂业务逻辑。

2)难以支持领域驱动设计,只能运用传统的面向对象设计。

3)数据库操作和业务逻辑通常放在服务层,但对于较大的系统或高并发情况下,这些操作可能会影响性能。

2. 充血模型

充血模型也被称为领域模型,是一种类似于面向对象的设计模型,其目的是将业务逻辑和数据结构紧密耦合在一起,将业务逻辑集成到实体中。这种模型强调在问题领域构建模型,并把问题领域的知识嵌入到代码中。它将业务逻辑和数据操作封装到实体中,实体包含了数据和操作数据的业务逻辑,以及实体之间的关系。

1. 充血模型/领域模型的概念

  • 描述:介绍什么是充血模型/领域模型以及其特点
  • 实现步骤:简单介绍什么是充血模型/领域模型,比如可以引用一些充血模型的定义,然后列举其特点,比如强调业务逻辑、紧密耦合、封装数据和业务逻辑等等
  • 作用:让读者了解充血模型/领域模型的基本概念,为后面的步骤打下基础

2. 问题领域的建模

  • 描述:介绍如何进行问题领域的建模
  • 实现步骤:具体描述如何进行问题领域的建模,比如可以引用DDD(领域驱动设计)的思想,从业务用户的角度出发,抽象出业务实体以及实体之间的关系,然后进一步细化业务实体的属性和行为。
  • 作用:让读者了解如何进行问题领域的建模,为后面充血模型的实现打下基础

问题领域建模是软件开发过程中非常重要的一步,它可以帮助开发者从业务用户的角度出发,抽象出业务实体以及实体之间的关系,并进一步细化业务实体的属性和行为,为后续的充血模型提供基础。

下面是一个简单的Java代码示例,用于说明如何进行问题领域建模:

//用户实体类
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String gender;
    
    public User(Long id, String name, Integer age, String gender) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    
    //getters and setters
}

//订单实体类
public class Order {
    private Long id;
    private User user;
    private List<Product> products;
    
    public Order(Long id, User user, List<Product> products) {
        this.id = id;
        this.user = user;
        this.products = products;
    }
    
    //getters and setters
}

//产品实体类
public class Product {
    private Long id;
    private String name;
    private BigDecimal price;
    
    public Product(Long id, String name, BigDecimal price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
    
    //getters and setters
}

//订单服务类,提供下单和取消订单的功能
public class OrderService {
    private OrderRepository orderRepository;
    
    public void placeOrder(User user, List<Product> products) {
        //创建订单实体
        Order order = new Order(idGenerator.generateId(), user, products);
        //保存订单
        orderRepository.save(order);
    }
    
    public void cancelOrder(Long orderId) {
        //从数据库中查询订单
        Order order = orderRepository.findById(orderId);
        //取消订单
        order.cancel();
        //保存订单
        orderRepository.save(order);
    }
}

//订单仓储接口
public interface OrderRepository {
    void save(Order order);
    Order findById(Long id);
}

这个示例中,我们从业务用户的角度出发,抽象出了三个业务实体:用户、产品和订单。订单实体中包含了用户和产品两个实体的引用,表示订单是由用户和产品组成的。订单服务类提供了下单和取消订单的功能,其中下单功能会创建订单实体并保存到数据库中,取消订单功能会取消订单并保存到数据库中。订单仓储接口定义了订单的保存和查询方法。这些类和方法都是从问题领域出发设计的,具有很好的可读性和可维护性,为后续的充血模型实现打下了基础。

3. 业务逻辑的封装

  • 描述:介绍如何将业务逻辑封装到实体中
  • 实现步骤:具体描述如何将业务逻辑封装到实体中,比如可以引用OO(面向对象)编程的思想,将实体看做对象,通过面向对象的方式封装业务逻辑。另外,也可以介绍如何使用领域服务等方式将业务逻辑封装到实体中。
  • 作用:让读者了解业务逻辑应该如何封装到实体中,为后面的步骤打下基础。

在面向对象编程中,我们可以将实体看做一个对象,通过对象的方法来封装业务逻辑。比如,我们可以定义一个学生类,将学生的基本信息属性和相关方法封装到类中:

public class Student {
    private String name;
    private int age;
    private String gender;
    
    public Student(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getGender() {
        return gender;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    // 业务逻辑:判断学生是否为成年人
    public boolean isAdult() {
        return age >= 18;
    }
}

在上面的代码中,我们定义了一个学生类,包含了学生的基本信息,以及一个判断学生是否为成年人的业务逻辑。通过将业务逻辑封装到实体中,我们可以将代码的复杂度降低,同时也更加符合面向对象编程的思想。

另外,我们也可以使用领域服务的方式来封装业务逻辑。领域服务是一种面向业务的服务,它将业务逻辑封装到服务中,以提供给其他业务组件使用。比如,我们可以定义一个学生服务,包含了对学生的操作:

public class StudentService {
    public boolean isAdult(Student student) {
        return student.getAge() >= 18;
    }
}

在上面的代码中,我们定义了一个学生服务,包含了一个判断学生是否为成年人的方法。通过使用领域服务的方式,我们可以将业务逻辑集中到一个服务中,使得代码更加清晰易懂。

总之,无论是采用面向对象编程的方式,还是使用领域服务的方式,将业务逻辑封装到实体中都是非常重要的。这不仅可以提高代码的可读性和可维护性,还可以更好地实现业务需求。

4. 数据操作的封装

  • 描述:介绍如何将数据操作封装到实体中
  • 实现步骤:具体描述如何将数据操作封装到实体中,比如可以使用ORM(对象关系映射)工具将数据库的表映射成实体类,让实体类来负责数据的操作。同时,也可以通过自定义方法来封装数据的操作。
  • 作用:让读者了解数据操作应该如何封装到实体中,为后面的步骤打下基础。

Java代码示例(基于ORM工具MyBatis):

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    // MyBatis注解,映射数据库操作
    @Select("SELECT * FROM user WHERE username = #{username}")
    public User findUserByUsername(String username) {
        // 调用MyBatis执行数据库操作
        return sqlSession.selectOne("findUserByUsername", username);
    }

    // 自定义方法,实现数据操作
    public void updateUserPassword(String password) {
        this.setPassword(password);
        // 调用MyBatis执行数据库更新操作
        sqlSession.update("updateUserPassword", this);
    }

    // getter和setter方法省略
}

// 调用示例
User user = new User();
user.setUsername("test");
user = user.findUserByUsername("test"); // 查询用户
user.updateUserPassword("new password"); // 更新用户密码

5. 实体之间的关系

  • 描述:介绍如何将实体之间的关系体现出来
  • 实现步骤:具体描述如何将实体之间的关系体现出来,比如可以使用面向对象的继承和组合的思想,实体之间的关系可以通过对象的属性或者方法来表示。另外,也可以通过聚合等方式来体现实体之间的关系。
  • 作用:让读者了解实体之间的关系应该如何体现出来,为后面的步骤打下基础。

Java代码示例:

  1. 继承关系
public class Animal {
    private int age;
    private String name;
    
    // getters and setters here
    
    public void eat() {
        System.out.println("Animal is eating.");
    }
}

public class Dog extends Animal {
    private String breed;
    
    // getters and setters here
    
    public void bark() {
        System.out.println("Dog is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.setAge(3);
        myDog.setName("Buddy");
        myDog.setBreed("Labrador");
        System.out.println("My " + myDog.getBreed() + " " + myDog.getName() + " is " + myDog.getAge() + " years old.");
        myDog.eat();
        myDog.bark();
    }
}
  1. 组合关系
public class Address {
    private String street;
    private String city;
    private String state;
    
    // getters and setters here
    
    public Address(String street, String city, String state) {
        this.street = street;
        this.city = city;
        this.state = state;
    }
}

public class Person {
    private String name;
    private int age;
    private Address address;
    
    // getters and setters here
    
    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    
    public String getAddressAsString() {
        return address.getStreet() + ", " + address.getCity() + ", " + address.getState();
    }
}

public class Main {
    public static void main(String[] args) {
        Address myAddress = new Address("123 Main St", "Anytown", "CA");
        Person myPerson = new Person("John", 30, myAddress);
        System.out.println(myPerson.getName() + " is " + myPerson.getAge() + " years old and lives at " + myPerson.getAddressAsString());
    }
}
  1. 聚合关系
public class Team {
    private String name;
    private List<Player> players;
    
    // getters and setters here
    
    public Team(String name, List<Player> players) {
        this.name = name;
        this.players = players;
    }
    
    public void printTeamInfo() {
        System.out.println("Team " + name + " has " + players.size() + " players:");
        for(Player player : players) {
            System.out.println("- " + player.getName() + " (" + player.getPosition() + ")");
        }
    }
}

public class Player {
    private String name;
    private String position;
    
    // getters and setters here
    
    public Player(String name, String position) {
        this.name = name;
        this.position = position;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Player> myPlayers = new ArrayList<Player>();
        myPlayers.add(new Player("John Doe", "Forward"));
        myPlayers.add(new Player("Jane Smith", "Midfielder"));
        myPlayers.add(new Player("Bob Johnson", "Defender"));
        Team myTeam = new Team("My Team", myPlayers);
        myTeam.printTeamInfo();
    }
}

以上代码示例中,第一个是继承关系,通过让Dog类继承Animal类,体现了Dog和Animal之间的关系;第二个是组合关系,Person类包含了Address类的实例作为其中一个属性,体现了Person和Address之间的关系;第三个是聚合关系,Team类包含了多个Player类的实例作为其中一个属性,体现了Team和Player之间的关系。这些关系的建立可以符合实际场景,更好地表示出实体之间的相互作用和联系。

6. 充血模型的实现

  • 描述:介绍如何实现充血模型
  • 实现步骤:结合前面的步骤,具体描述如何实现充血模型,将业务逻辑和数据操作封装到实体中,并体现实体之间的关系。一般来说,可以使用OO编程语言来实现充血模型。
  • 作用:让读者了解如何实现充血模型,帮助读者将前面的步骤串联起来,形成一个完整的充血模型的实现流程。

充血模型是一种实现业务逻辑和数据操作封装到实体中的编程模型。下面给出Java代码示例,演示如何实现充血模型。

首先定义一个实体类,包含业务逻辑和数据操作。以订单为例:

public class Order {
    private int id;
    private Date createdTime;
    private List<OrderItem> items;
    private boolean paid;
    
    // 构造方法
    public Order(int id) {
        this.id = id;
        this.createdTime = new Date();
        this.items = new ArrayList<>();
        this.paid = false;
    }
    
    // 添加订单项
    public void addItem(Product product, int quantity) {
        OrderItem item = new OrderItem(product, quantity);
        items.add(item);
    }
    
    // 计算订单总价
    public double getTotalPrice() {
        double total = 0;
        for (OrderItem item : items) {
            total += item.getProduct().getPrice() * item.getQuantity();
        }
        return total;
    }
    
    // 支付订单
    public void pay() {
        if (!paid) {
            // 扣除用户的余额
            User user = getCurrentUser();
            user.setBalance(user.getBalance() - getTotalPrice());
            // 修改订单状态为已支付
            paid = true;
        }
    }
    
    // 获取当前用户
    private User getCurrentUser() {
        // 这里省略获取当前用户的代码
        return new User("张三", 1000);
    }
    
    // 内部类:订单项
    private class OrderItem {
        private Product product;
        private int quantity;
        
        // 构造方法
        public OrderItem(Product product, int quantity) {
            this.product = product;
            this.quantity = quantity;
        }
        
        // 获取商品
        public Product getProduct() {
            return product;
        }
        
        // 获取数量
        public int getQuantity() {
            return quantity;
        }
    }
}

在Order类中,我们封装了添加订单项、计算订单总价和支付订单等业务逻辑,同时也封装了订单的状态和订单项的信息。这样,我们就实现了将业务逻辑和数据操作封装到实体中的目的。

另外,在Order类中,我们还定义了一个内部类OrderItem,用来表示订单项。这也体现了实体之间的关系。

在实际应用中,我们可以通过调用Order类的方法来实现订单的创建、支付等操作。例如:

// 创建订单
Order order = new Order(1);
order.addItem(new Product("手机", 2999), 2);
order.addItem(new Product("平板", 3999), 1);

// 计算订单总价
double totalPrice = order.getTotalPrice();

// 支付订单
order.pay();

这样,我们就通过Java代码示例演示了如何实现充血模型。充血模型的实现可以帮助我们更好地封装业务逻辑和数据操作,从而实现更加灵活、可维护和可扩展的代码。

总之,这样的拆分和细化,有助于让读者更好地理解充血模型的实现过程,并将其应用到具体的开发工作中。

充血模型的优点

1)实体拥有更多的业务逻辑,更符合用户需求。

2)代码结构清晰,易于维护和升级。

3)支持领域驱动设计,适用于复杂场景。

充血模型的缺点

1)实现复杂度高。

2)由于实体与业务逻辑强相关,导致模型的灵活性较差。

3)在大型项目或高并发情况下,可能会影响性能。

3. 贫血模型与充血模型的比较

从以上的介绍可以看出,贫血模型和充血模型都有自己的优缺点。两者的本质区别在于数据和业务逻辑的处理方式。在贫血模型中,数据对象仅包含数据和简单的操作,业务逻辑在服务层中实现。因此,贫血模型更适用于简单业务场景,开发效率高,但对于复杂业务或大型项目并不适用。

而在充血模型中,实体包含数据和业务逻辑,实体与实体之间的关系也更为紧密。这种设计模型可以更好地满足业务需求,但同时也会增加实现的复杂性。在大型项目或高并发应用场景中,可能会影响性能。

4. 如何选择?

在选择贫血模型还是充血模型时,首先需要考虑业务需求和场景。如果业务相对简单,那么贫血模型可能是更好的选择,因为它的实现简单、容易理解,同时可以提高开发效率。但如果业务较为复杂,或者需要支持领域驱动设计,那么充血模型可能更适合。

另外,考虑到性能问题,对于对性能要求较高的场景,可以选择贫血模型,并在必要时进行优化。例如将一些频繁的数据操作放在缓存中,或使用一些高效的数据结构等。

总之,选择贫血模型还是充血模型需要根据具体业务需求来进行权衡,但无论选择哪种模型,都需要保持代码结构清晰、易于维护和升级。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员廖志伟

赏我包辣条呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值