Лучший способ добавить функциональность к встроенным типам

#java #oop

#java #ооп

Вопрос:

Интересно, какой наилучший способ с точки зрения строгого ООП добавить функциональность к встроенным типам, таким как строки или целые числа или более сложные объекты (в моем случае класс BitSet).

Если быть более конкретным — у меня есть два сценария:

  1. Добавление метода хэширования md5 к объекту String
  2. Добавление методов преобразования (таких как fromByteArray() или ToInteger()) в класс BitSet.

Теперь мне интересно, какими были бы наилучшие методы для реализации этого.

Я мог бы, например, создать новый класс «BitSetEx», расширяющийся из BitSet, и добавить свои методы. Но мне не нравится идея, поскольку для этого нового класса потребуется описывающее имя, а «BitSetWithConversionMethods» звучит действительно глупо.

Теперь я мог бы написать класс, состоящий только из статических методов, выполняющих преобразования.

Что ж, у меня есть много идей, но я не хочу знать, что было бы «лучшим» в смысле ООП.

Итак, кто-нибудь может ответить мне на этот вопрос?

Ответ №1:

Здесь есть несколько подходов:

Во-первых, вы могли бы придумать лучшее название для extends BitSet класса. Нет, BitsetWithConversionMethods это не очень хорошее название, но, возможно, что-то вроде ConvertibleBitSet есть. Передает ли это намерение и использование класса? Если да, то это хорошее название. Аналогично у вас может быть HashableString (имея в виду, что вы не можете расширить String , как указывает Энтони в другом ответе). Этот подход именования дочерних классов с помощью XableY (или XingY , как BufferingPort или SigningEmailSender ) иногда может быть полезным для описания добавления нового поведения.

Тем не менее, я думаю, что в вашей проблеме (невозможность найти имя) есть справедливый намек на то, что, возможно, это не очень хорошее дизайнерское решение, и оно пытается сделать слишком много. Обычно хорошим принципом проектирования является то, что класс должен «делать что-то одно». Очевидно, что в зависимости от уровня абстракции это может быть расширено, чтобы включать что угодно, но об этом стоит подумать: считаются ли «манипулирование установленным / сброшенным состоянием нескольких битов» и «преобразование битового шаблона в другой формат» как одно и то же? Я бы сказал, что (особенно с намеком на то, что вам трудно придумать название) это, вероятно, две разные обязанности. Если это так, то наличие двух классов в конечном итоге сделает их чище, их будет проще поддерживать (другое правило гласит, что «у класса должна быть одна причина для изменения»; у одного класса для обоих манипулирования преобразования есть как минимум 2 причины для изменения), Их будет проще тестировать изолированно и т.д.

Итак, не зная вашего дизайна, я бы предложил, возможно, два класса; в BitSet примере есть как a BitSet , так и (скажем) a BitSetConverter , который отвечает за преобразование. Если вы хотели по-настоящему наворочаться, возможно, даже:

 interface BitSetConverter<T> {
 T convert(BitSet in);
 BitSet parse(T in);
}
  

тогда у вас может быть:

 BitSetConverter<Integer> intConverter = ...;
Integer i = intConverter.convert(myBitSet);
BitSet new = intConverter.parse(12345);
  

который действительно изолирует ваши изменения, делает каждый отдельный конвертер тестируемым и т.д.

(Конечно, как только вы это сделаете, вам может захотеться взглянуть на guava и рассмотреть возможность использования функции, например a Function<BitSet, Integer> для одного случая и Function<Integer, BitSet> для другого. Тогда вы получаете целую экосистему Function поддерживающего кода, который может оказаться полезным)

Ответ №2:

Я бы выбрал расширяющий класс. Это на самом деле то, что вы делаете, расширяя текущий класс некоторыми дополнительными методами.

Что касается названия: вам не следует называть at для новых функций, поскольку вы можете добавить больше позже. Это ваш расширенный класс BitSet, так что BitSetEx allready звучит лучше, чем то, BitSetWithConversionMethods что вы предлагаете.

Вы не хотите писать класс со статическими методами, это похоже на процедурное программирование в среде ООП и считается неправильным. У вас есть объект, у которого есть определенные методы (такие fromByteArray() , которые вы хотите создать), поэтому вы хотите, чтобы эти методы были в этом классе. Расширение — это правильный путь.

Ответ №3:

Это зависит. Как указала Нанне, подкласс — это вариант. Но только иногда. Строки объявлены окончательными, поэтому вы не можете создать подкласс. У вас есть как минимум 2 других варианта:

1) Используйте «инкапсуляцию», т. Е. создайте класс myString, у которого есть строка, с которой он работает (в отличие от расширения String, чего вы не можете сделать). По сути, оболочка вокруг строки, которая добавляет вашу функциональность.

2) Создайте утилиту / хелпер, то есть класс, содержащий только статические методы, которые работают со строками. Итак, что-то вроде

 class OurStringUtil {
....
    public static int getMd5Hash(String string) {...}
....

}
  

Взгляните на материал Apache StringUtils, он следует этому подходу; это замечательно.

Ответ №4:

«Лучший способ» довольно субъективен. И имейте в виду, что String является конечным классом, поэтому вы не можете его расширить. Два возможных подхода — это написание оболочек, таких как StringWrapper(String) с вашими дополнительными методами, или какой-то StringUtils класс, полный статических методов (начиная с Java 5, статические методы могут быть импортированы, если вы не хотите напрямую использовать класс util).