返回

Java家谱数据硬编码:问题、方案与最佳实践

java

Java 中硬编码家谱数据的几种方式

直接把值写死在代码里(硬编码)这种做法,在搞 Java 项目,尤其是要弄一个家谱应用的时候,时不时会遇到。想弄个初始化的家谱数据,又不知道从哪儿下手,这确实挺让人头疼的。下面我来给你掰扯掰扯,分析一下硬编码的各种姿势,再给你支几招,保管你能把这事儿给办漂亮了。

一、 硬编码带来的问题

硬编码一时爽,代码改起来可就麻烦了。想想看,如果你直接把家谱成员的信息(名字、性别、住址啥的)都写死在代码里,会有啥问题?

  1. 改起来费劲: 要是家谱里某人的信息变了,比如搬家了,或者名字写错了,你得找到代码里对应的地方,一个一个改。家谱小还好,要是家谱大了,几百号人,你改到猴年马月去?
  2. 数据和逻辑混一块: 把数据和处理数据的逻辑都搅和在一起,代码看起来乱糟糟的,以后想加个新功能,比如搜索某个成员,都不知道从哪儿下手。
  3. 不好测试: 硬编码的数据,让测试变得很麻烦。你得手动去检查每个成员的信息是不是对的,太不方便了。

二、 硬编码家谱数据的几种方案

既然硬编码有这么多毛病,那我们该怎么处理家谱数据呢?下面我给你介绍几种不同的硬编码方式,你可以根据自己的实际情况选择合适的方案。

1. 直接在 main 方法或者初始化方法里创建对象

这是最直接粗暴的方式。直接用 new 创建 FamilyMember 对象,然后设置各个属性,再把这些对象连接起来,构成一个家谱。

原理: 这种方法的好处是很直观, 容易理解。
坏处上面说了

代码示例:

public class FamilyTreeApp {

    public static void main(String[] args) {
        // 创建家庭成员对象
        FamilyMember grandfather = new FamilyMember("张三", "张", null, "1950-2020", Gender.MALE, new Address("北京"));
        FamilyMember grandmother = new FamilyMember("李四", "李", null, "1952-2022", Gender.FEMALE, new Address("北京"));
        FamilyMember father = new FamilyMember("张伟", "张", null, "1975-", Gender.MALE, new Address("上海"));
        FamilyMember mother = new FamilyMember("王芳", "王", "张", "1978-", Gender.FEMALE, new Address("上海"));
        FamilyMember son = new FamilyMember("张小明", "张", null, "2000-", Gender.MALE, new Address("上海"));

        // 设置亲属关系
        grandfather.setSpouse(grandmother);
        grandmother.setSpouse(grandfather);

        father.setFather(grandfather);
        father.setMother(grandmother);
        father.setSpouse(mother);
        mother.setSpouse(father);

        son.setFather(father);
        son.setMother(mother);

        grandfather.addChild(father);
        grandmother.addChild(father);
        father.addChild(son);
        mother.addChild(son);

         // 初始化家谱树
         TreeImpl familyTree = new TreeImpl(grandfather);

        // 打印家庭成员信息 (这里只是个例子,你可以根据需要修改)
        System.out.println("爷爷: " + familyTree.getRoot().getFirstName());
         System.out.println("家庭成员数量 " + familyTree.getAllMembers().size());
          System.out.println("儿子: " + familyTree.getDetailsForMember("张小明").getFirstName());
    }
}

//Gender 和 Address 需要自己提前定义好. 这里只是示例.
enum Gender {
    MALE, FEMALE
}

class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }
    //为了打印输出好看,可以添加一个tostring
      @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                '}';
    }
}

安全建议:

  • 这种方式没啥特别的安全问题,就是记得把代码写规范了。

2. 使用配置文件

把家谱数据放到一个单独的配置文件里,比如 JSONXML 或者 properties 文件。然后在程序启动的时候,读取配置文件,把数据加载到内存里。

原理: 这样做的好处是,数据和代码分离了。以后想修改数据,直接改配置文件就行了,不用动代码。

代码示例 (使用 JSON 文件):

首先,创建一个 family_tree.json 文件,内容如下:

[
    {
        "firstName": "张三",
        "surName": "张",
        "surNameAfterMarriage": null,
        "life": "1950-2020",
        "gender": "MALE",
        "address": {"city": "北京"},
        "spouse": "李四",
        "children": ["张伟"]
    },
    {
        "firstName": "李四",
        "surName": "李",
        "surNameAfterMarriage": null,
        "life": "1952-2022",
        "gender": "FEMALE",
        "address": {"city": "北京"},
        "spouse": "张三",
        "children": ["张伟"]
    },
    {
        "firstName": "张伟",
        "surName": "张",
        "surNameAfterMarriage": null,
        "life": "1975-",
        "gender": "MALE",
        "address": {"city": "上海"},
        "spouse": "王芳",
        "children": ["张小明"],
        "father": "张三",
        "mother": "李四"
    },
    {
        "firstName": "王芳",
        "surName": "王",
        "surNameAfterMarriage": "张",
        "life": "1978-",
        "gender": "FEMALE",
        "address": {"city": "上海"},
        "spouse": "张伟",
        "children": ["张小明"],
        "father": null,
        "mother": null
    },
    {
        "firstName": "张小明",
        "surName": "张",
        "surNameAfterMarriage": null,
        "life": "2000-",
        "gender": "MALE",
        "address": {"city": "上海"},
        "spouse": null,
        "children": [],
        "father": "张伟",
        "mother": "王芳"
    }
]

然后,在 Java 代码中读取这个 JSON 文件,并解析成 FamilyMember 对象:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

public class FamilyTreeApp {

    public static void main(String[] args) throws IOException {

        // 使用 Jackson 库读取 JSON 文件
        ObjectMapper mapper = new ObjectMapper();
        List<FamilyMember> members = mapper.readValue(new File("family_tree.json"), new TypeReference<List<FamilyMember>>() {});

         //构建ID Map. 方便后续查找
         Map<String, FamilyMember> memberMap = new HashMap<>();
        for (FamilyMember member : members) {
             memberMap.put(member.getFirstName(), member); // 这里假设FirstName 是唯一的. 实际可能用ID
         }

         //关联亲属关系
        for(FamilyMember member : members){
             if (member.getSpouse() != null && !member.getSpouse().getFirstName().isEmpty() ) {
                member.setSpouse(memberMap.get(member.getSpouse().getFirstName()));
            }
            if(member.getFather() !=null  && !member.getFather().getFirstName().isEmpty()){
                member.setFather(memberMap.get(member.getFather().getFirstName()));
            }

            if(member.getMother() !=null && !member.getMother().getFirstName().isEmpty()){
              member.setMother(memberMap.get(member.getMother().getFirstName()));
            }

            if (member.getChildren() != null ) {
                for(int i = 0; i< member.getChildren().size(); i++){
                  FamilyMember child = member.getChildren().get(i);
                  if(child !=null && !child.getFirstName().isEmpty() ){
                    member.getChildren().set(i, memberMap.get(child.getFirstName()));
                  }
                }

           }

        }
       TreeImpl familyTree = new TreeImpl(members.get(0)); //假设第一个人是根节点

        System.out.println("爷爷: " + familyTree.getRoot().getFirstName());
        System.out.println("家庭成员数量 " + familyTree.getAllMembers().size());
        System.out.println("儿子: " + familyTree.getDetailsForMember("张小明").getFirstName());
    }

}

注意: 上面的代码用了 Jackson 库来解析 JSON。你需要先添加 Jackson 的依赖。

pom.xml 里加上:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.0</version>  </dependency>

安全建议:

  • 如果配置文件里有敏感信息,比如身份证号、住址等,要做好加密和权限控制。

3. 使用数据库

如果你的家谱数据比较多,或者需要经常更新,可以考虑把数据放到数据库里,比如 MySQL、PostgreSQL 或者 SQLite。

原理: 数据库能帮你高效地存储和管理大量数据。用 SQL 语句就能方便地查询、修改和删除数据。

代码示例 (使用 SQLite):

这里简单起见用SQLite,省去了安装数据库的麻烦。当然,实际项目中你完全可以根据自己的情况选用MySQL,Oracle等。

首先, 下载 SQLite JDBC 驱动 (例如 sqlite-jdbc-*.jar),把它加到你的项目里。

然后,创建一个 SQLite 数据库文件 (例如 family_tree.db)。你可以用 SQLite 的命令行工具或者图形化工具来创建。

接着,创建一个表来存储家谱数据:

CREATE TABLE family_members (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    first_name TEXT,
    sur_name TEXT,
    sur_name_after_marriage TEXT,
    life TEXT,
    gender TEXT,
    city TEXT,
    spouse_id INTEGER,
    father_id INTEGER,
    mother_id INTEGER,
    FOREIGN KEY (spouse_id) REFERENCES family_members(id),
    FOREIGN KEY (father_id) REFERENCES family_members(id),
    FOREIGN KEY (mother_id) REFERENCES family_members(id)
);

然后,插入一些数据:

INSERT INTO family_members (first_name, sur_name, sur_name_after_marriage, life, gender, city, spouse_id, father_id, mother_id) VALUES
('张三', '张', NULL, '1950-2020', 'MALE', '北京', 2, NULL, NULL),
('李四', '李', NULL, '1952-2022', 'FEMALE', '北京', 1, NULL, NULL),
('张伟', '张', NULL, '1975-', 'MALE', '上海', 4, 1, 2),
('王芳', '王', '张', '1978-', 'FEMALE', '上海', 3, NULL, NULL),
('张小明', '张', NULL, '2000-', 'MALE', '上海', NULL, 3, 4);

接下来,在 Java 代码里连接数据库,查询数据,并创建 FamilyMember 对象:

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class FamilyTreeApp {

    public static void main(String[] args) throws SQLException {

       String url = "jdbc:sqlite:family_tree.db"; //数据库连接地址
        List<FamilyMember> allMembers = new ArrayList<>();

       try (Connection conn = DriverManager.getConnection(url);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM family_members")) {

            //循环每一行记录
            while (rs.next()) {
              //读取数据
                String firstName = rs.getString("first_name");
                String surName = rs.getString("sur_name");
                String surNameAfterMarriage = rs.getString("sur_name_after_marriage");
                String life = rs.getString("life");
                String genderStr = rs.getString("gender");
                Gender gender = Gender.valueOf(genderStr); // 把字符串转换成枚举
                String city = rs.getString("city");
               Address address = new Address(city);

                 FamilyMember member = new FamilyMember(firstName,surName, surNameAfterMarriage,life, gender,address );
                member.setMemberID(rs.getInt("id"));
                allMembers.add(member);
             }
         }

      //再次循环, 这次构建亲属关系.  这里假设数据库里ID是唯一的.
      try (Connection conn = DriverManager.getConnection(url);
             Statement stmt = conn.createStatement()) {

           for(FamilyMember member: allMembers){
                int spouseId =  getSpouseId(conn, member.getMemberID());
                int fatherId = getFatherId(conn, member.getMemberID());
                int motherId = getMotherId(conn, member.getMemberID());

                if(spouseId>0) member.setSpouse(findMemberById(allMembers, spouseId));
                if(fatherId>0) member.setFather(findMemberById(allMembers, fatherId));
                if(motherId>0) member.setMother(findMemberById(allMembers, motherId));

               List<Integer> childrenIds = getChildrenIds(conn, member.getMemberID());
                for(int childId: childrenIds){
                  member.addChild(findMemberById(allMembers, childId));
                }
           }
      }

         TreeImpl familyTree = new TreeImpl(allMembers.get(0)); //假设第一个人是根

         System.out.println("爷爷: " + familyTree.getRoot().getFirstName());
         System.out.println("家庭成员数量 " + familyTree.getAllMembers().size());
          System.out.println("儿子: " + familyTree.getDetailsForMember("张小明").getFirstName());
    }
  //辅助方法: 根据成员ID, 在列表中查找成员.
  private static FamilyMember findMemberById(List<FamilyMember> members, int id) {
      for (FamilyMember member : members) {
          if (member.getMemberID() == id) {
              return member;
          }
      }
      return null; //没找到
  }
   private static int getSpouseId(Connection conn, int memberId) throws SQLException {
    String sql = "SELECT spouse_id FROM family_members WHERE id = ?";
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
      pstmt.setInt(1, memberId);
      try (ResultSet rs = pstmt.executeQuery()) {
        if (rs.next()) {
          return rs.getInt("spouse_id");
        }
      }
    }
    return -1; // 没有配偶
  }

  private static int getFatherId(Connection conn, int memberId) throws SQLException {
        String sql = "SELECT father_id FROM family_members WHERE id = ?";
      try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, memberId);
           try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    return rs.getInt("father_id");
                 }
           }
        }
    return -1; //
  }
      private static int getMotherId(Connection conn, int memberId) throws SQLException {
         String sql = "SELECT mother_id FROM family_members WHERE id = ?";
          try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
           pstmt.setInt(1, memberId);
             try (ResultSet rs = pstmt.executeQuery()) {
                 if (rs.next()) {
                       return rs.getInt("mother_id");
                  }
            }
          }
       return -1;
   }
      private static List<Integer> getChildrenIds(Connection conn, int memberId) throws SQLException{
           List<Integer> childrenIds = new ArrayList<>();
          String sql = "SELECT id FROM family_members WHERE father_id = ? OR mother_id = ?";
            try(PreparedStatement pstmt = conn.prepareStatement(sql)){
                pstmt.setInt(1, memberId);
                 pstmt.setInt(2, memberId);
                 try(ResultSet rs = pstmt.executeQuery()){
                        while (rs.next()){
                             childrenIds.add(rs.getInt("id"));
                          }
                    }
           }
         return childrenIds;

    }

}

安全建议:

  • 连接数据库的用户名和密码要妥善保管,不要直接写在代码里。可以用环境变量或者专门的配置管理工具。
  • 使用预编译语句 (PreparedStatement),防止 SQL 注入攻击。
  • 对数据库做好备份。

进阶使用技巧

以上三种方法都可以实现硬编码初始化家谱树, 下面有一些更高级的玩法:

  • 构建器模式 (Builder Pattern): 如果 FamilyMember 对象的创建过程比较复杂,比如有很多可选属性,或者属性之间有依赖关系,可以考虑使用构建器模式。这样可以让对象的创建过程更清晰,更易于维护。

  • 工厂模式 (Factory Pattern): 如果你需要创建不同类型的家庭成员,比如普通成员、VIP 成员等,可以考虑使用工厂模式。

总而言之呢, 选哪种方法取决于你的实际情况和需求。数据量小、改动不频繁,直接在代码里创建对象就行;数据量大、经常改,就用数据库。关键是,要让你的代码干净、好维护、易扩展。