Что такое композиция, и как она связана с наследованием? Какие преимущества она имеет перед последним? Какие подводные камни имеет этот приём?
Наследование - расширение одного класса другим.
Когда следует использовать наследование:
1.Когда программисты знают детальное устройство всей ветки, от которой они наследуются.
ИЛИ
2.Когда расширяемые классы специально созданы и документированы для последующего расширения. То есть в документации должны быть отражены все условия, при которых класс может вызвать переопределяемый метод, какие его методы вызывают друг друга.
НО ОБЯЗАТЕЛЬНО
Опасности использования наследования:
1. Мы можем не знать, что переписываемый метод может вызываться в другом методе суперкласса, что приводит к ошибкам в вычислениях. Ярким примером является создание счетчика вызовов добавления элементов в HashSet -
class InstrumentedHashSet extends HashSet{
private int addCount = 0;
//Пропуск конструктора. Перейдем к сути
//…
public boolean add(Object o){
addCount++;
super.add(o);
}
public boolean addAll(Collection c){
addCount+=c.size();
super.addAll(c);
}
//… Другие методы
}
Когда мы вызываем метод addAll InstrumentedHashSet-а, то мы добавляем размер коллекции и вызываем метод addAll() HashSet, который содержит внутри метод add(). Он, в свою очередь, вызывает переписанный метод! В итоге нам отдадут число, которое в 2 раза больше ожидаемого. Такое «использование самого себя» является деталью реализации, и нет гарантии, что она не поменяется от одной версии к другой. Это можно поменять, просто поставив цикл с вызовом add(), но это является повторением кода, и вообще - можно что-то упустить. Этот вариант сложен, трудоемок и подвержен ошибкам.
Суперкласс в новой версии может обзавестись некоторыми методами. Предположим, что у нас есть система безопасности. У нас есть подкласс, который контролирует добавление новых элементов(переопределяет методы, в них проверяет соответствие элементов каким-то нормам). Выходит новый метод, который имеет некую строку, которая просто добавляет строку напрямую. Итог: защита имеет брешь, систему можно взломать.
Если суперкласс заимеет метод, который есть в одном из подклассов, то, в лучшем случае, когда аргументы не совпадают, то программа просто не скомпилируется. В худшем случае мы просто будем получать неверный результат, не зная где ошибка.
Все дефекты суперкласса перейдут в API подкласса, в то время как композиция позволяет разработать новый API, который скрывает эти недостатки. Поэтому вы обязаны хорошо продумать реализацию, структуру, так как есть только один шанс что-то изменить. Нужно пробовать разные варианты структур, реализаций.
Мы можем случайно наследовать класс, который вообще для этого не заточен, который либо не документирован для наследования, либо вообще не документирован. Решение — делать такие классы final, чтобы от него нельзя было наследоваться. Это, кстати, даст небольшой прирост в скорости.
Правила проектирования наследования:
1 Конструкторы суперкласса не должны вызывать переопределяемые методы непосредственно или опосредованно. Нарушение влечет аварийное завершение программы.
class Subclass extends ParentNew{
Date date;
Subclass(){
date = new Date();
parentMethodThatOverrides();//вызывает переписанный метод, все в порядке
}
//…
@Override
public void parentMethodThatOverrides(){
System.out.println("date in incorrect method: "+date);
}
}
class ParentNew{
ParentNew(){
parentMethodThatOverrides();//вызывает не свой, а переписанный метод!
}
public void parentMethodThatOverrides(){}
}
С реализацией интерфейсов Cloneable или Serializable в классе, предназначенном для наследования, нужно понять, что, поскольку методы clone() и readObject() в значительной степени работают как конструкторы, к ним применимо то же самое ограничение: ни методу clone(), ни методу readObject() нельзя разрешать вызывать переопределяемый метод, непосредственно или опосредованно. При реализации Serializable в классе-наследнике нужно методы readResolve или writeReplace сделать не закрытыми, а защищенными, чтобы подклассы(класса-наследника) не игнорировали эти методы.
Но есть лекарство от этих проблем. Имя ему — композиция
Композиция — создание private(!) объекта «суперкласса» в подклассе. Этот подкласс называется классом-оболочкой Передача вызова -- каждый экземпляр метода в новом классе вызывает соответствующий метод содержащегося здесь же экземпляра прежнего класса, а затем возвращает полученный результат. Соответствующие методы нового класса носят название методов переадресации.
class InstrumentedHashSet{
private int addCount = 0;
HashSet hashSet;
//…
InstrumentedHashSet(Collection c){
hashSet = new HashSet(c);
}
//...
public boolean add(Object o){
addCount++;
return hashSet.add(o);
}
public boolean addAll(Collection c){
addCount+=c.size();
return hashSet.addAll(c);
}
//… Другие методы
}
Преимущества:
Полученный класс будет прочен, как скала: он не будет зависеть от деталей реализации прежнего класса. Даже если к имевшемуся прежде классу будут добавлены новые методы, на новый класс это не повлияет. Данная реализация не только устойчива, но и чрезвычайно гибка. Мы просто преобразуем один класс(HashSet) в другой, добавляя некоторый функционал.
Недостаток:
Классы-оболочки обладают проблемой самоидентификации(SELF problem). Классы-оболочки не приспособлены для использования в схемах с обратным вызовом(callback framework), где один объект передает другому объекту ссылку на самого себя для последующего вызова. Так как объекту все равно на свою оболочку он входит в этот метод и его вызовы, переписанные в классе-оболочке, минуют перепись в классе-оболочки, и, как результат, используются непереписанные методы.
class Wrapper implements SomethingWithCallback {
private final WrappedObject wrappedObject;
//...
@Override
public void doSomething() {
wrappedObject.doSomething();
}
@Override
public void call() {
System.out.println("Wrapper callback!");
}
//...
}
class WrappedObject implements SomethingWithCallback {
private final SomeService service;
//...
@Override
public void doSomething() {
service.callSelf(this); // Комментарий
}
@Override
public void call() {
System.out.println("WrappedObject callback!");
}
//...
}
class SomeService{
void callSelf(WrappedObject Object){
Object.call();
}
}
Комментарий - здесь он дает ссылку на СЕБЯ(для метода это объект WrappedObject, а не объект WrappedObject, завернутый в оболочку подкласса!). Естественно вызывется call() WrappedObject-а, так как теперь его ничто не переписывает.
Виртуальный выделенный сервер (VDS) становится отличным выбором
Имеется массив из байтов одной Java программыКак мне получить её исходники в нормальном Java виде, с помощью ASM может как-то ?
Написал Логин Фильтр, который не будем пропускать не авторизованного юзера :