Где в Android нарушаются принципы SOLID ?
Недавно у ученика на собеседовании был такой вопрос, без предварительной подготовки довольно сложно быстро сориентироваться и назвать примеры по всем принципам.
💚 Решение 💚
1️⃣ Single Responsibility
У класса должна быть только одна причина для изменения.
Чтобы найти нарушителя, нам нужен какой-то класс, который имеет несколько ответственностей, т.е. один занимается разными вещами. Пример - класс Context - содержит много разных ответственностей, ниже указана часть его методов:
// работа с компонентами
public abstract void startActivity(Intent var1);
public abstract ComponentName startService(Intent var1);
// работа с ресурсами
public final int getColor(int id)
public final Drawable getDrawable(int id)
// работа с файлами
public abstract FileInputStream openFileInput(String var1)
public abstract boolean deleteFile(String var1);
Какое улучшение к этому дизайну можно предложить: имело бы смысл выделить дополнительный слой абстракции под каждую группу методов, а сам Context мог бы управлять этими группами, примерно такие могли бы быть вызовы:
context.components.startActivity()
context.permissions.checkPermission()
context.files.deleteFile()
2️⃣ Open - Closed
Классы должны быть открыты для расширения, но закрыты от модификации.
Намеков на отклонение от этого принципа является использование множественные if-конструкции в коде.Например, в классе View можно увидеть метод dispatchActivityResult c кодом
if (who == null) {
onActivityResult(requestCode, resultCode, data);
} else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) {
//...
} else if (who.startsWith("@android:view:")) {
//...
} else if Напрашивается рефакторинг через создание специального интерфейса:
interface ActivityResultDispatcher {
fun canDispatch(who: String)
fun dispatchResult(...)
}..и использования итерирования по массиву этих объектов, вместо if-конструкций
val dispatchers: List<ActivityResultDispatcher>
for(dispatcher in dispatchers) {
if (dispatcher.canDispatch(who)) {
dispatcher.dispatchResult(...)
}
}
Как результат - легко будет добавить или удалить новый кейс без внесения модификации в сам класс.
3️⃣ Liskov Substitution
Реализация может быть подставлена вместо абстракции, код не должен сломаться.
Намек на отклонение от принципа: явное приведение параметра к какой-то реализации. Пример - класс ResourcesImpl с методом:
TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
AttributeSet set,
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
final XmlBlock.Parser parser = (XmlBlock.Parser) set;
//..
}
при передаче в качестве AttributeSet любой имплементации кроме XmlBlock.Parser произойдет ClassCastException
4️⃣ Interface Segregation
Интерфейс не должен быть слишком раздутым, лучше иметь несколько более узкоспециализированных интерфейсов.
Намек на отклонение от принципа: при реализации интерфейса несколько методов часто остаются пустыми, используется один метод. В этом случае имеет смысл разделения интерфейсов:
public interface TextWatcher {
public void beforeTextChanged(...);
// практически всегда нужен только этот метод
public void onTextChanged(...);
public void afterTextChanged(...);
}5️⃣ Dependency Inversion
Абстракции не должны зависеть от реализации, реализация должна зависеть от абстракций.
Здесь отклонением от принципа может быть неявная зависимость на какой-то объект
class Fragment {
FragmentManager mChildFragmentManager = new FragmentManagerImpl();
}Неявную зависимость невозможно подменить, что создает проблемы с расширением возможностей класса.
P.S. как вам такой формат? 🔥 - если было полезно, буду чаще разбирать вопросы с собесов