Книга: JavaScript. Подробное руководство, 6-е издание
9.7.4. Иерархии классов и абстрактные классы
9.7.4. Иерархии классов и абстрактные классы
В предыдущем разделе было предложено «предпочесть композицию наследованию». Но для иллюстрации этого принципа мы создали подкласс класса Set
. Сделано это было для того, чтобы получившийся подкласс был instanceof Set
и наследовал полезные методы класса Set
, такие как toString()
и equals().
Это достаточно уважительные причины, но, тем не менее, было бы неплохо иметь возможность использовать прием композиции без необходимости наследовать некоторую определенную реализацию множества, такую как класс Set
. Аналогичный подход можно было бы использовать и при создании класса SingletonSet
(пример 9.12) -этот класс был определен как подкласс класса Set
, чтобы унаследовать вспомогательные методы, но его реализация существенно отличается от реализации суперкласса. Класс SingletonSet
- это не специализированная версия класса Set
, а совершенно иной тип множеств. В иерархии классов SingletonSet
должен был бы находиться на одном уровне с классом Set
, а не быть его потомком.
Решение этой проблемы в классических объектно-ориентированных языках, а также в языке JavaScript
заключается в том, чтобы отделить интерфейс от реализации. Представьте, что мы определили класс AbstractSet
, реализующий вспомогательные методы, такие как toString(),
в котором отсутствуют реализации базовых методов, таких как foreach().
Тогда все наши реализации множеств - Set, SingletonSet
и FilteredSet
- могли бы наследовать класс AbstractSet
. При этом классы FilteredSet
и SingletonSet
больше не наследовали бы ненужные им реализации.
Пример 9.16 развивает этот подход еще дальше и определяет иерархию абстрактных классов множеств. Класс AbstractSet
определяет только один абстрактный метод, contains().
Любой класс, который претендует на роль множества, должен будет определить хотя бы один этот метод. Далее в примере определяется класс AbstractEnumerableSet
, наследующий класс AbstractSet
. Этот класс определяет абстрактные методы size()
and foreach()
и реализует конкретные вспомогательные методы (toString(), toArray(), equals()
и т.д.). AbstractEnumerableSet
не определяет методы add()
или remove()
и представляет класс множеств, доступных только для чтения. Класс SingletonSet
может быть реализован как конкретный подкласс. Наконец, в примере определяется класс AbstractWritableSet
, наследующий AbstractEnumerableSet
. Этот последний абстрактный класс определяет абстрактные методы add()
и remove()
и реализует конкретные методы, такие как union()
и intersection(),
использующие их. Класс AbstractWritableSet
отлично подходит на роль суперкласса для наших классов Set
и FilteredSet
. Однако они не были добавлены в пример, а вместо них была включена новая конкретная реализация с именем ArraySet
.
Пример 9.16 довольно объемен, но он заслуживает детального изучения. Обратите внимание, что для простоты создания подклассов в нем используется функция
Function.prototype.extend().
Пример 9.16. Иерархия абстрактных и конкретных классов множеств
// Вспомогательная функция, которая может использоваться для определения
// любого абстрактного метода
function abstractmethod() { throw new Error("абстрактный метод"); }
/*
* Класс AbstractSet определяет единственный абстрактный метод, contains().
*/
function AbstractSet() {
throw new Error("Нельзя создать экземпляр абстрактного класса");
}
AbstractSet.prototype.contains = abstractmethod;
/*
* NotSet - конкретный подкласс класса AbstractSet.
* Элементами этого множества являются все значения, которые не являются
* элементами некоторого другого множества. Поскольку это множество
* определяется в терминах другого множества, оно не доступно для записи,
* а так как оно имеет бесконечное число элементов, оно недоступно для перечисления.
* Все, что позволяет этот класс, - это проверить принадлежность к множеству.
* Обратите внимание, что для определения этого подкласса используется метод
* Function.prototype.extendO, объявленный выше.
*/
var NotSet = AbstractSet.extend(
function NotSet(set) { this.set = set; },
{
contains: function(x) { return !this.set.contains(x); },
toString: function(x) { return "~" + this.set.toString(); },
equals: function(that) {
return that instanceof NotSet && this.set.equals(that.set);
}
}
/*
* AbstractEnumerableSet - абстрактный подкласс класса AbstractSet.
* Определяет абстрактные методы size() и foreach() и реализует конкретные
* методы isEmptyO. toArrayO, to[Locale]String() и equals().
* Подклассы, реализующие методы contains(), size() и foreach(),
* получают эти пять конкретных методов даром.
*/
var AbstractEnumerableSet = AbstractSet.extend(
function() {
throw new Error("Нельзя создать экземпляр абстрактного класса");
},
{
size: abstractmethod,
fоreach: abstractmethod,
isEmpty: function() { return this.size() == 0; },
toString: function() {
var s = і = 0;
this.foreach(function(v) {
if (i++ > 0) s += ", ";
s += v;
});
return s +
},
toLocaleString : function() {
var s = "{", і = 0;
this.foreach(function(v) {
if (i++ > 0) s += ", "
if (v == null) s += v; // null и undefined
else s += v. toLocaleString(); // все остальные
});
return s +
},
toArray: function() {
var a = [];
this.foreach(function(v) { a.push(v); });
return a;
},
equals: function(that) {
if (!(that instanceof AbstractEnumerableSet)) return false;
// Если множество that имеет другой размер, множества не равны
if (this.size() ! = that.sizeO) return false;
// Проверить наличие каждого элемента this в множестве that,
try {
this.foreach(function(v){
if (!that.contains(v)) throw false;}
);
return true; // Все элементы одинаковые: множества равны.
} catch (х) {
if (х === false) return false; // Множества не равны
throw х; // Повторно возбудить любое иное возникшее исключение.
}
}
});
/*
* SingletonSet - конкретный подкласс класса AbstractEnumerableSet.
* Множество из единственного элемента, доступное только для чтения.
*/
var SingletonSet = AbstractEnumerableSet.extend(
function SingletonSet(member) { this.member = member; },
{
contains: function(x) { return x === this.member; },
size: function() { return 1; },
foreach: function(f,ctx) { f.call(ctx, this.member); }
}
/*
* AbstractWritableSet - абстрактный подкласс класса AbstractEnumerableSet.
* Определяет абстрактные методы add() и remove() и реализует конкретные
* методы union(), intersection() и difference().
*/
var AbstractWritableSet = AbstractEnumerableSet.extend(
function() {
throw new Error("Нельзя создать экземпляр абстрактного класса");
},
{
add: abstractmethod,
remove: abstractmethod,
union: function(that) {
var self = this;
that.foreach(function(v) { self.add(v); });
return this;
},
intersection: function(that) {
var self = this;
this.foreach(function(v){
if(!that.contains(v)) self.remove(v);
});
return this;
},
difference: function(that) {
var self = this;
that.foreach(function(v) { self.remove(v); });
return this;
}
});
/*
* ArraySet - конкретный подкласс класса AbstractWritableSet.
* Представляет множество элементов как массив значений и реализует линейный
* поиск в массиве в своем методе contains(). Поскольку алгоритм метода containsO
* имеет сложность 0(п) вместо 0(1), данный класс следует использовать только
* для создания относительно небольших множеств.
* Обратите внимание, что эта реализация опирается на методы класса Array
* indexOfO и forEach(), которые определяются стандартом ES5.
*/
var ArraySet = AbstractWritableSet.extend(
function ArraySet() {
this.values = [];
this.add.apply(this, arguments);
},
{
contains: function(v) {
return this.values.indexOf(v) != -1;
},
size: function() {
return this.values.length;
},
foreach: function(f.c) {
this.values.forEach(f, c);
},
add: function() {
for(var і = 0; і < arguments.length; i++) {
var arg = arguments[i];
if (Ithis.contains(arg)) this.values.push(arg);
}
return this;
},
remove: function() {
for(var і = 0; і < arguments.length; i++) {
var p = this.values.indexOf(arguments[i]);
if (p == -1) continue;
this.values.splice(p, 1);
)
return this;
}
}
- 7.16. Абстрактные классы
- Лекция № 12. Связи классов сущностей
- ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ НА JAVA 7. КЛАССЫ
- 9.7. Подклассы
- Принудительный полиморфизм: абстрактные методы
- Абстрактные классы
- 8.4.3. Перемещение по иерархии: nftw()
- Построение иерархии интерфейсов
- 9.2. Классы и конструкторы
- 9.1. Классы и прототипы
- Классы сертификатов
- Статические классы