Равенство объектов vs уникальность - PullRequest
0 голосов
/ 06 августа 2020

В моем хобби-проекте Kotlin я столкнулся с дилеммой, как реализовать метод equals () в случае такого класса, как:

// I'm using Kotlin-like syntax    
class ConfigurationParameter {
    val name: String    // used for command line option, *.conf file parameter, ENV variable, ...
    val allowedValues: Set<String>      // the valid values of the configuration parameter
    val description: String     // used in --help, as a comment above parameter in *.conf file, ...
}

Equality

Теперь из моего POV, два объекта этого класса равны, только если они равны по всем своим свойствам. В противном случае они будут вести себя иначе:

  • В случае name ... это совершенно другой параметр.
  • В случае allowedValues ​​... проверка будет отличаться.
  • В случае описания ... напечатанная справка по использованию будет отличаться.

Уникальность

В то же время мне не нужны два объекта с одинаковым именем (но, возможно, с отдельными allowedValues ​​или описанием), чтобы появиться в одном наборе (Set<ConfigurationParameter>). Это может привести к таким проблемам, как дублирование параметров командной строки и т.п.

Этого не должно происходить

Я знаю, что не следует создавать два параметра конфигурации с одним и тем же именем и разными другими свойства в приложении в первую очередь. Но давайте рассмотрим это как некий внутренний механизм самопроверки.

Решение

Единственное решение, к которому я пришел, - это создать новый ConfigurationParameterSet (не основанный на Set), который обрабатывает «одинаковость» его элементов по их имени, а не по их методу equals ().

Проблема с этим решением состоит в том, что должен быть такой новый класс Set для каждого класса сущности, который имеет равенство, отличное от его уникальность.

Вопрос

Есть ли какое-нибудь устоявшееся общее c решение этой дилеммы равенства и уникальности?

Ответы [ 2 ]

3 голосов
/ 07 августа 2020

Вместо вашего настраиваемого класса, подобного множеству, вы можете использовать Map, который использует свойство name в качестве ключей. Вы также можете добавить функции расширения, чтобы использовать его как Set. В Java вам нужно будет расширить класс, чтобы добавить их.

fun MutableMap<String, ConfigurationParameter>.add(parameter: ConfigurationParameter) =
    put(parameter.name, parameter)

fun MutableMap<String, ConfigurationParameter>.remove(parameter: ConfigurationParameter) =
    remove(parameter.name, parameter)

operator fun Map<String, ConfigurationParameter>.contains(parameter: ConfigurationParameter) =
    containsValue(parameter)

Если у вас много таких классов, где вы хотите сохранить их с помощью свойства name, вы можете сделать интерфейс со свойством name, которое все они могут использовать, а затем создать указанную выше функцию расширения для любой карты, которая использует значения, реализующие интерфейс:

interface NamedItem { val name: String }

class ConfigurationParameter: NamedItem {
    override val name: String,
    val allowedValues: Set<String>,
    val description: String
}

fun <T: NamedItem> MutableMap<String, T>.add(parameter: T) =
    put(parameter.name, parameter)

fun <T: NamedItem> MutableMap<String, T>.remove(parameter: T) =
    remove(parameter.name, parameter)

operator fun <T: NamedItem> Map<String, T>.contains(parameter: T) =
    containsValue(parameter)
1 голос
/ 06 августа 2020

Я плохо разбираюсь в Kotlin, но эта проблема звучит точно так же в Java. В Java у вас есть два типа равенства: (1) ссылочное равенство (a == b), где a и b являются ссылками на один и тот же объект, и (2) hashCode / равно равенству. Я подозреваю, что когда вы говорите об «уникальности», вы имеете в виду не ссылочное равенство, а скорее понятие равенства хеш / равно, когда все поля одинаковы.

То, что у вас есть, не является языковой проблемой. Это проблема дизайна. Вам нужно решить, что делает два объекта равными ИЛИ выбрать другой подход.

Итак, один из способов сделать это - определить такой метод, как:

enum Similarity { FULL, NAME }
boolean same(Object object, Similarity similarity)

Затем вы можете вызвать то же самое ( ) из equals (), чтобы придать сходство по умолчанию. Вы также можете представить себе, как сделать объект модальным, в котором он имеет состояние подобия, а метод equals использует это состояние, чтобы решить, какой тип подобия использовать. Обратной стороной этого состояния является (1) проблема подобия / равенства не обязательно лучше всего определяется методами в самом классе (разделение проблем) и (2) изменяемое состояние не лучшее, если вы можете его избежать.

Другой, возможно, лучший подход может заключаться в создании двух реализаций компаратора, в которых один компаратор использует только имя, а другой - все значения. Это очень распространенный подход в Java и должен быть таким же простым в Kotlin. Компараторы задают порядок сортировки, но возвращаемое значение 0 указывает на равенство. Если вы предпочитаете логическое значение, вы можете использовать тот же метод, но создать интерфейс, например:

interface SimilarityComparator
{
    boolean same(Object a, Object b)
}

BTW, если вы реализуете компаратор как вложенный класс, вы можете увеличить инкапсуляцию, избавившись от необходимости раскрывать свойство значения или поля для сравнения (методы получения и установки свойств плохие, см. Алан Холуб).

https://www.baeldung.com/java-comparator-comparable

Надеюсь, это поможет.

Jon
...