将处理 class 所有实例的 HashMap 的方法放在单独的 class 中

Putting methods that handle HashMap of all instances of a class in a separate class

我有一个创建索引卡的 class,在其中,我有一个静态实例变量 HashMap,它存储所有创建的实例。

我想了很多,我认为处理 HashMap 操作的方法应该放在不同的 class 中,因为这些方法不直接在任何索引卡上操作,他们对索引卡列表进行操作。

这样,我会有一个 IndexCard class 和一个 ListAdministrator class。并且两个 classes 将处理不同的功能。

问题是这个新的class(ListAdministrator)只有静态方法,因为只有一个列表,没有理由创建任何新的索引卡列表,我只需要一个。

我应该将这些方法移到另一个 class 还是应该保持这样?这是一个好习惯吗?

这是代码:

class IndexCard {
    public static HashMap <String, IndexCard> list = new HashMap <> ();
    public String name;
    public String address;
    public String phone;
    public String email;
    public LocalDate dateRegister;

    IndexCard(String name, String dni, String address, String phone, String email) {
        this.name = name;
        this.address = address;
        this.phone = phone;
        this.email = email;
        dateRegister = LocalDate.now();
        if (Utils.validarDni(dni) && !list.containsKey(dni)) {
            list.put(dni, this);
        } else {
            throw new InvalidParameterException ("Error when entering the data or the DNI has already been previously registered");
        }
    }

    /**
     * Update the data of the selected card.
     */
    public void update() throws IllegalAccessException {
        String key = getKeyWithObject(this);
        Scanner reader = new Scanner(System.in);
        Field[] fields = this.getClass().getFields();
        for (Field field: fields) {
            String nameField = Utils.splitCamelCase(field.getName());
            if (!Modifier.isStatic(field.getModifiers()) && (field.getType()).equals(String.class)) {
                System.out.println ("Enter new " + nameField);
                String value = reader.nextLine().trim();
                field.set(this, value);
            }
        }
        reader.close();
        list.put(key, this);
        System.out.println("Updated data \n \n");
    }

    /**
     * Delete the selected card.
     */
    public void delete() throws IllegalAccessException {
        String key = getKeyWithObject(this);
        Field [] fields = this.getClass().getFields();
        for (Field field: fields) {
            if (!Modifier.isStatic(field.getModifiers())) {
                field.set(this, null);
            }
        }
        list.remove(key);
    }

    /**
     * Displays the data of the selected card on screen.
     */
    public void print() throws IllegalAccessException {
        Field [] fields = this.getClass().getFields();
        for (Field field: fields) {
            if (!Modifier.isStatic(field.getModifiers())) {
                String nameFieldConSpaces = Utils.splitCamelCase(field.getName());
                Object value = field.get(this);
                System.out.println(nameFieldConSpaces + ":" + value);
            }
        }
    }

    /**
     * Print all the entries of the desired sublist with the ID, Name and phone number.
     */
    public static <T extends IndexCard> void SubClasslist (Class <T> subClass) {
        for (HashMap.Entry <String, IndexCard> entry: list.entrySet ()) {
            String key = entry.getKey ();
            IndexCard card = entry.getValue ();
            if (card.getClass().equals(subClass)) {
                System.out.println ("ID:" + key + "| Name:" + card.name + "| Phone:" + card.phone);
            }
        }
    }

    /**
     * Returns the object stored in the list of cards when entering the corresponding key.
     */
    public static IndexCard GetObjetWithKey(String key) {
        try {
            return list.get(key);
        } catch (IllegalArgumentException e) {
            System.out.println (e + ": The indicated key does not appear in the database.");
            return null;
        }
    }

    /**
     * Obtain the Key when entering the corresponding card.
     */
    public static String getKeyWithObject (Object obj) {
        for (HashMap.Entry <String, IndexCard> entry: list.entrySet()) {
            if (obj.equals(entry.getValue())) {
                return entry.getKey();
            }
        }
        throw new IllegalArgumentException ("The indicated data does not appear in the database, and therefore we could not obtain the key.");
    }

    /**
     * Returns a list of cards when entering the main data of the card.
     * @param data Corresponds to the identifying name of the file.
     */
    public static ArrayList <IndexCard> SearchByName (String data) {
        try {
            ArrayList <IndexCard> listCards = new ArrayList <> ();
            for (HashMap.Entry <String, IndexCard> entry: list.entrySet ()) {
                IndexCard card = entry.getValue ();
                String name = entry.getValue().name;
                if (name.toLowerCase().trim().contains(data.toLowerCase().trim())) {
                listCards.add(card);
                }
            }
            return listCards;
        } catch (IllegalArgumentException e) {
            System.out.println (e + "The indicated data does not appear in the database, you may have entered it incorrectly.");
            return null;
        }
    }
}

所有这些静态方法都是我要放入新 class.

中的内容

这就是新 class ListAdministrator 的样子。它甚至不需要构造函数。

class ListAdministrator{
    public static HashMap <String, IndexCard> list = new HashMap <> ();

    /**
     * Print all the entries of the desired sublist with the ID, Name and phone number.
     */
    public static <T extends IndexCard> void SubClasslist (Class <T> subClass) {
        for (HashMap.Entry <String, IndexCard> entry: list.entrySet ()) {
            String key = entry.getKey ();
            IndexCard card = entry.getValue ();
            if (card.getClass().equals(subClass)) {
                System.out.println ("ID:" + key + "| Name:" + card.name + "| Phone:" + card.phone);
            }
        }
    }

    /**
     * Returns the object stored in the list of cards when entering the corresponding key.
     */
    public static IndexCard GetObjetWithKey(String key) {
        try {
            return list.get(key);
        } catch (IllegalArgumentException e) {
            System.out.println (e + ": The indicated key does not appear in the database.");
            return null;
        }
    }

    /**
     * Obtain the Key when entering the corresponding card.
     */
    public static String getKeyWithObject (Object obj) {
        for (HashMap.Entry <String, IndexCard> entry: list.entrySet()) {
            if (obj.equals(entry.getValue())) {
                return entry.getKey();
            }
        }
        throw new IllegalArgumentException ("The indicated data does not appear in the database, and therefore we could not obtain the key.");
    }

    /**
     * Returns a list of cards when entering the main data of the card.
     * @param data Corresponds to the identifying name of the file.
     */
    public static ArrayList <IndexCard> SearchByName (String data) {
        try {
            ArrayList <IndexCard> listCards = new ArrayList <> ();
            for (HashMap.Entry <String, IndexCard> entry: list.entrySet ()) {
                IndexCard card = entry.getValue ();
                String name = entry.getValue().name;
                if (name.toLowerCase().trim().contains(data.toLowerCase().trim())) {
                listCards.add(card);
                }
            }
            return listCards;
        } catch (IllegalArgumentException e) {
            System.out.println (e + "The indicated data does not appear in the database, you may have entered it incorrectly.");
            return null;
        }
    }
}

由于单一职责原则,您应该将管理 IndexCards 和 IndexCards 本身的关注点分开。此外,ListAdministrator 应该处理与 IndexCards 管理有关的所有事情,还包括管理对象的删除和创建。

名称 ListAdministrator 不符合要求,因为它不管理列表,可能使用类似 IndexCardRegistry 的名称。

要处理并发,您可以使用 ConcurrentMap 作为主要数据存储。

如果您的 IndexCards 需要访问它或其他 IndexCards,让 ListAdministrator 全部静态可能会派上用场,但这不是最佳设计。他们还需要知道吗?根据我的理解,IndexCards 可能是简单的 POJO,它只包含数据而根本不包含任何逻辑。

另一方面,如果不对代码进行重大重构,使用全静态 ListAdministrator 以后您将无法同时使用两个托管对象实例。即使你今天永远不会想到这一点,一个可以处理任何对象的定义良好的对象注册表可能会在未来的项目中派上用场。因此,我宁愿为 ListAdministrator 使用真实实例(并针对它的界面进行编程以保持灵活性)。

更详细的参考您的评论:

这种方法的想法是将关注点清楚地分开,这将使未来对代码的更改成为可能,以防项目增长(大多数项目倾向于这样做)。我的理解是 ListAdministrator 应该管理您的 IndexCards。在某种程度上,这与对象关系映射器的工作方式相同,但目前您的数据库是一个 HashMap。如果您为 ListAdministrator 创建一个接口,您甚至可以用数据库替换 HashMap 而无需更改其客户端。

在对您的代码进行第二次调查时,我发现 IndexCards 不仅存储数据,而且还有更新数据的方法。这代表了对单一职责原则的又一次破坏,应该加以处理。如果 ListAdministrator 会为给定的 IndexCard 提供更新方法,您可以想到它可以被许多不同的客户端使用,而无需更改 ListAdministrators API 背后的任何代码。您的第一个客户端将是您已经编程的命令行界面,下一个可能是 Web 服务。

有了全静态的 ListAdministrator,您就有了一个管理一个静态数据集的静态 Class。它总是只处理 IndexCards,你添加的所有内容都将在同一个 HashMap 中结束(如果 allowed/compatible)。可以访问 class ListAdministrator 的应用程序的每个部分都可以完全访问数据。如果您需要另一个用于不同类型的 ListAdministrator(处理创建、删除、更新、搜索),您将不得不重构所有内容以适应这种情况或开始复制代码。为什么不首先创建一个基于实例的解决方案。您将拥有 IndexCards 的存储库,并可以随意添加新的存储库。

也许这对您的用例来说是过度工程化,但在保持责任明确分离的过程中,您会发现代码的许多扩展将发生正交(不影响现有代码),这才是乐趣真正开始的地方.如果不是较小的项目,你想如何练习。

更多关于灵活代码使用接口的原因(回应最新评论)

简短的回答是:始终针对接口进行编码(如许多文章和 java 书中所述)。但是为什么?

Java 接口就像 class 与其客户之间的合同。它定义了一些方法,但并不自己实现它们。要实现一个接口,您可以定义一个 class 和 class XYZ implements SomeInterface,并且 class 的源代码会做任何它认为合理的事情来回答接口中定义的方法。您尝试保持接口小,只包含基本方法,因为接口越小,必须进行更改时需要考虑的方法就越少。

Java 中的一个常见习语是为方法定义 List<T> return 类型(接口),这很可能是 ArrayList(具体 class),但也可以是 LinkedList(另一个具体 class),或者任何其他实现 List 接口的东西。通过仅 returning List 接口,您可以防止您的客户端使用其他 returned 具体 class 的其他方法,这将大大减少您更改 class 内部实现的自由=69=]。您隐藏了内部实现,但同意 return 满足给定接口的东西。如果您想承担更少的义务,您可以 return 界面 Iteratable 而不是 List.

查看 Java API,您会发现标准 class 像 ArrayList 实现了许多接口。您始终可以在内部使用 ArrayList 并 return 它作为完成这项工作可能的最小接口。

回到你的项目。必须通过其界面而不是其具体 class 来引用注册表 (ListAdministrator)。该接口将定义像

这样的方法
interface IndexCardRegistry {

     void delete(Long id) throws IllegalAccessException;

     void update(Long id, Some data) throws IllegalAccessException;

     // ...
}

它所做的与客户无关,它只是希望一切顺利。因此,如果客户端调用存储库更新方法,它将依赖存储库来更新目标 IndexCard。存储库可以根据需要存储数据,在 HashMapList 甚至数据库中,这对客户来说无关紧要。

class IndexCardMapBasedRegistry implements IndexCardRegistry {

    private Map store = new HashMap();

    void delete(Long id) throws IllegalAccessException {
        // code to remove the IndexCard with id from the hashmap 
    }

    void update(Long id, Some data) throws IllegalAccessException {
        // code to get the IndexCard with id from 
        // the hashmap and update its contents 
    }

    // ...

}

现在是新的迭代,在创建注册表时将 IndexCardMapBasedRegistry 换成新的

class IndexCardDatabaseRegistry implements IndexCardRegistry {

     private Database db;


    void delete(Long id) throws IllegalAccessException {
        // code to remove the IndexCard with id from the database 
    }

    void update(Long id, Some data) throws IllegalAccessException {
        // code to update the IndexCard with id in the database 
    }

    // ...

}

IndexCardRegistry indexCards = new IndexCardMapBasedRegistry(); 变成 IndexCardRegistry indexCards = new IndexCardDatabaseRegistry();

客户端不能完全改变,但注册表将能够处理一定数量的索引卡,否则会耗尽您的计算机内存。

继续使用 IndexCard class,不需要创建新的 class ListAdministrator 在 class IndexCard 中,你有 hashmap 类型的列表,它在内存数据结构中表示,你在这个 class 中有 n 个方法可以在这个数据结构中工作,所以我建议留在单个 class 因为它将承担单一职责。