Clean Recycler Adapter. Частина 1

Введення


Списки, списки, списки… Вертикальні, горизонтальні, комбіновані. Практично ні один мобільний додаток не обходиться без них. Більше того, нерідко додатки складаються з одних тільки списків.

І якщо в «однорідних» списках немає нічого страшного, то різні типи комірок вже можуть викликати питання, основні з яких:

  • як полегшити зміна і масштабування типів комірок
  • як мінімізувати кількість місць для зміни, знизивши ризик потенційних помилок
  • як позбутися if-else каліцтва
  • як позбутися потворних перевірок на тип і небезпечних привидів типів

Що не так з if-else
У цілому в «ифах» (сюди відносяться конструкції виду if-else switch-case) немає нічого страшного… поки вони використовуються для бінарного вибору (наприклад, горезвісна перевірка на нулл). Але коли кількість варіантів перевищує дві відповіді, то це вже привід задуматися, що не так з руками кодом і як це можна виправити.

Так чому велика кількість операторів вибору це погано?

Ну по-перше, тому що «ифы» в одному місці практично завжди породжують «ифы» і в інших місцях коду. Таким чином отримуємо від одного до нескінченності (межі) місць для редагування. І у разі необхідності внесення змін дуже просто забути змінити «ще одне місце».

По-друге, якщо знову подивитися на ситуацію в межі, то ми можемо отримати нескінченну кількість варіантів вибору. Що в коді «основного» класу буде виглядати потворно і стане місцем потенційних (і дуже ймовірних) помилок.

Ну і по-третє, безліч «ифов» — це по-суті моветон, ознака того, що з архітектурної точки зору все не так вже й райдужно як могло здатися на перший погляд.

Ну і як же можна виправити ситуацію?

Існує принаймні два шляхи вирішення проблеми.

По-перше, скористатися так званим «табличним методом» (Стів Макконнелл «Досконалий код») і замінити логіку вибору набором підготовлених даних. Що по-перше, позбавляє від обсягу потворного коду, а по-друге, дозволяє використовувати зовнішні джерела для надання цих самих даних, тим самим позбавляючи від необхідності внесення виправлень у сам код.

По-друге, можна використовувати патерн фабрика (Банда Чотирьох «Патерни проектування») і инкапсулировать логіку вибору в одному місці — у фабриці (крім основного обов'язку — приховування породження нових однотипних об'єктів — фабрика також може инкапсулировать і логіку вибору). Це не позбавляє від «ифов» повністю, як попередній метод, але дозволяє скоротити кількість таких місць до одного. Відповідно, код стає більш красивим і легко підтримується, оскільки у разі внесення змін це потрібно буде зробити рівно в одному місці.

Що не так з перевіркою на тип
Перевірка на тип сама по собі не несе нічого поганого. Більш того, переглядаючи вихідний код навіть від найбільш великих гравців в світі андроїда, я часто натикався на такі перевірки.

Але все ж перевірка на тип, на мій погляд, це упущення в архітектурі (до речі, Скотт Майерс зі мною солідарний). І якщо є можливість позбавитися від таких перевірок, то це обов'язково потрібно зробити.

Як?

Перше що приходить на розум це вже знайомий «табличний метод». Можна, наприклад, підготувати колекцію типу map, де заздалегідь задати відповідність типів.

І друге. Але тут вже немає таких чітких рекомендацій. Все буде залежати від конкретного випадку. Можна спробувати використовувати Java Узагальнення де це можливо. Можна дуже уважно подивитися на таку властивість системи як поліморфізм. Як говориться, «Interfaces still working everywhere».

Що не так з приведенням типів
Хоч приведення типів можна зустріти навіть в Android SDK (наприклад, findViewById() або getSystemService()), це не робить цю процедуру безпечною. Приведення типів завжди несе в собі потенційну загрозу падіння програми ClassCastException.

Обернення «кастов» в блоки try-catch не кращий вихід. По-перше, сама ця конструкція виглядає досить потворно. А по-друге, таку проблему досить непросто відловлювати, т. к. падінь немає, а додаток поводиться непередбачувано.

Як варіантНепоганим рішенням, до речі, тут буде настройка Fabric на відправку всіх non-fatal винятків. Бувають ситуації, коли робота на «правильне» рішення переважують вигоду від його використання. Тому, як я вже неодноразово повторював, це привід задуматися як можна виправити ситуацію. І якщо рішення занадто «дороге», то… це привід замислитися.

У будь-якому випадку, приведення типів не кращий вибір. І краще його уникати.

Як?

З основних рецептів це Java Узагальнення і поліморфізм. Також не зайвим буде врахувати існування патерну Відвідувач (Банда Чотирьох «Патерни проектування»).

«Традиційний» підхід
З проблемами розібралися. Тепер давайте згадаємо, як вирішується завдання показу «різнорідного» списку «традиційним» способом. «Традиційний» він тому що по-перше, інтернет підказує нам діяти саме так, а по-друге, за моїми особистими спостереженнями, переважна кількість джуніорів і неподавляющее кількість мідл саме так і діють.

Наприклад, маємо три типи комірок:

ProgressVo.java
/**
* Just a marker for progress header/footer.
*/
public class ProgressVo {
}


AdVo.java
public class AdVo {
private String title;
private String description;

// Getters, Setters, Builder, etc.
}


UserVo.java
public class UserVo {
private String firstName;
private String lastName;
private String age;

// Getters, Setters, Builder, etc.
}


Спочатку необхідно оголосити константи під кожен тип комірки:

private static final int TYPE_PROGRESS = 10;
private static final int TYPE_AD = 20;
private static final int TYPE_USER = 30;

Далі відповідно потрібно визначити тип комірки для кожної конкретної позиції (щоб уникнути пекла з розрахунком позицій, при довільних місцях різнотипних клітинок у списку, простіше всього використовувати нетипизированную колекцію):

@Override
public int getItemViewType(int position) {
Object item = itemList.get(position);
if (item instanceof ProgressVo) {
return TYPE_PROGRESS;
} else if (item instanceof AdVo) {
return TYPE_AD;
} else if (item instanceof UserVo) {
return TYPE_USER;
} else {
throw new NoSuchRecyclerItemTypeException();
}
}

І створити відповідний холдер:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_PROGRESS) {
View view = inflater.inflate(R. layout.cell_progress, parent, false);
return new UsersRecyclerAdapter.ProgressViewHolder(view);
} else if (viewType == TYPE_AD) {
View view = inflater.inflate(R. layout.cell_ad, parent, false);
return new UsersRecyclerAdapter.AdViewHolder(view);
} else if (viewType == TYPE_USER) {
View view = inflater.inflate(R. layout.cell_user, parent, false);
return new UsersRecyclerAdapter.UserViewHolder(view);
} else {
throw new NoSuchRecyclerViewTypeException();
}
}

А після ще й зв'язати наш холдер з даними:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ProgressViewHolder) {
// Do nothing.
} else if (holder instanceof AdViewHolder) {
((AdViewHolder) holder).bind((AdVo) itemList.get(position));
} else if (holder instanceof UserViewHolder) {
((UserViewHolder) holder).bind((UserVo) itemList.get(position));
}
}

Весь клас виглядає наступним чином:

UsersUglyRecyclerAdapter.java
public class UsersUglyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_PROGRESS = 10;
private static final int TYPE_AD = 20;
private static final int TYPE_USER = 30;

private List itemList = new ArrayList();

public UsersUglyRecyclerAdapter() {
itemList.add(new ProgressVo());
}

@Override
public int getItemViewType(int position) {
Object item = itemList.get(position);
if (item instanceof ProgressVo) {
return TYPE_PROGRESS;
} else if (item instanceof AdVo) {
return TYPE_AD;
} else if (item instanceof UserVo) {
return TYPE_USER;
} else {
throw new NoSuchRecyclerItemTypeException();
}
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_PROGRESS) {
View view = inflater.inflate(R. layout.cell_progress, parent, false);
return new UsersRecyclerAdapter.ProgressViewHolder(view);
} else if (viewType == TYPE_AD) {
View view = inflater.inflate(R. layout.cell_ad, parent, false);
return new UsersRecyclerAdapter.AdViewHolder(view);
} else if (viewType == TYPE_USER) {
View view = inflater.inflate(R. layout.cell_user, parent, false);
return new UsersRecyclerAdapter.UserViewHolder(view);
} else {
throw new NoSuchRecyclerViewTypeException();
}
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ProgressViewHolder) {
// Do nothing.
} else if (holder instanceof AdViewHolder) {
((AdViewHolder) holder).bind((AdVo) itemList.get(position));
} else if (holder instanceof UserViewHolder) {
((UserViewHolder) holder).bind((UserVo) itemList.get(position));
}
}

@Override
public int getItemCount() {
return itemList.size();
}

public void setUsers(List<UserVo> users) {
itemList.clear();
itemList.addAll(users);
decorateItemList();
notifyDataSetChanged();
}

private void decorateItemList() {
int listSize = itemList.size();
int shift = 0;
for (int i = 1; i < listSize; i++) {
if (i % 7 == 0) {
itemList.add(i + shift, new AdVo());
shift++;
}
}
itemList.add(new ProgressVo());
}

protected static class ProgressViewHolder extends RecyclerView.ViewHolder {

public ProgressViewHolder(View itemView) {
super(itemView);
}
}

protected static class AdViewHolder extends RecyclerView.ViewHolder {

public AdViewHolder(View itemView) {
super(itemView);
}

public void bind(AdVo ad) {
// Bind ad...
}
}

protected static class UserViewHolder extends RecyclerView.ViewHolder {

public UserViewHolder(View itemView) {
super(itemView);
}

public void bind(UserVo user) {
// Bind user...
}
}
}


Що ми маємо в підсумку? Три місця з логікою вибору, велика кількість «ифоф», перевірок на тип та приведення типів. У разі нобходимости внесення змін в нас аж 4 місця для цього (без урахування створення нового холдера при масштабуванні).

Як би непорядок і все таке. Давайте розбиратися як можна виправити ситуацію.

Більш «чистий» підхід
Шляхів вирішення означеної проблеми може бути декілька. В рамках даної статті ми використовуємо адаптацію «табличного методу», де в якості «таблиці» буде виступати enum.

Наша мета — привести код в «однорядковий» вигляд:

@Override
public int getItemViewType(int position) {
return CellType.get(itemList.get(position)).type();
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return CellType.get(viewType).viewHolder(parent);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
CellType.get(itemList.get(position)).bind(holder, itemList.get(position));
}

Отже, для початку нам потрібно визначити типи використовуваних осередків:

private enum CellType {
PROGRESS,
AD,
USER
}

Почнемо з того, що нам необхідно для визначення типу комірки і створення відповідного холдера:

private enum CellType {
PROGRESS {
@Override
int type() {
return R. layout.cell_progress;
}

@Override
RecyclerView.ViewHolder viewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_progress, parent, false);
return new ProgressViewHolder(view);
}
},
AD {
@Override
int type() {
return R. layout.cell_ad;
}

@Override
RecyclerView.ViewHolder viewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_ad, parent, false);
return new AdViewHolder(view);
}
},
USER {
@Override
int type() {
return R. layout.cell_user;
}

@Override
RecyclerView.ViewHolder viewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_user, parent, false);
return new UserViewHolder(view);
}
};

abstract int type();

abstract RecyclerView.ViewHolder viewHolder(ViewGroup parent);
}

Хочу звернути увагу, що в якості viewType використовується id розмітки осередку. Таким чином по-перше, немає потреби у визначенні констант, і по-друге, унікальні id виключають конфліктні ситуації. Деякі бібліотеки можуть резервувати під себе певні константи або ж поточний code-base робить це. А такі речі легко забуваються, що в підсумку призводить до неприємних наслідків.

Т. к. android SDK в методах getItemViewType() onBindViewHolder() використовує позицію елемента в колекції, а в методі onCreateViewHolder() змінну viewType, то нам буде потрібно два методи для отримання відповідного enum:

private enum CellType {
PROGRESS {
@Override
boolean is(Object item) {
return item instanceof ProgressVo;
}
...
},
AD {
@Override
boolean is(Object item) {
return item instanceof AdVo;
}
...
},
USER {
@Override
boolean is(Object item) {
return item instanceof UserVo;
}
...
};

static CellType get(Object item) {
for (CellType cellType : CellType.values()) {
if (cellType.is(item)) {
return cellType;
}
}
throw new NoSuchRecyclerItemTypeException();
}

static CellType get(int viewType) {
for (CellType cellType : CellType.values()) {
if (cellType.type() == viewType) {
return cellType;
}
}
throw new NoSuchRecyclerViewTypeException();
}

abstract boolean is(Object item);
...
}

Метод is() в даному випадку використовується тільки для внутрішніх потреб".

Залишилося тільки зв'язати холдер з даними:

private enum CellType {
PROGRESS {
...
@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
// Do nothing.
}
},
AD {
...
@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
try {
AdViewHolder adViewHolder = (AdViewHolder) holder;
AdVo ad = (AdVo) item;
adViewHolder.bind(ad);
} catch (ClassCastException e) {
L. printStackTrace(e);
}
}
},
USER {
...
@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
try {
UserViewHolder userViewHolder = (UserViewHolder) holder;
UserVo user = (UserVo) item;
userViewHolder.bind(user);
} catch (ClassCastException e) {
L. printStackTrace(e);
}
}
};
...
abstract void bind(RecyclerView.ViewHolder holder, Object item);
}

Одержаний клас виглядає наступним чином:

UsersRecyclerAdapter.java
public class UsersRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List itemList = new ArrayList();

public UsersRecyclerAdapter() {
itemList.add(new ProgressVo());
}

@Override
public int getItemViewType(int position) {
return CellType.get(itemList.get(position)).type();
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return CellType.get(viewType).viewHolder(parent);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Object item = itemList.get(position);
CellType.get(item).bind(holder, item);
}

@Override
public int getItemCount() {
return itemList.size();
}

public void setUsers(List<UserVo> users) {
itemList.clear();
itemList.addAll(users);
decorateItemList();
notifyDataSetChanged();
}

private void decorateItemList() {
int listSize = itemList.size();
int shift = 0;
for (int i = 1; i < listSize; i++) {
if (i % 7 == 0) {
itemList.add(i + shift, new AdVo());
shift++;
}
}
itemList.add(new ProgressVo());
}

private enum CellType {
PROGRESS {
@Override
boolean is(Object item) {
return item instanceof ProgressVo;
}

@Override
int type() {
return R. layout.cell_progress;
}

@Override
RecyclerView.ViewHolder viewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_progress, parent, false);
return new ProgressViewHolder(view);
}

@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
// Do nothing.
}
},
AD {
@Override
boolean is(Object item) {
return item instanceof AdVo;
}

@Override
int type() {
return R. layout.cell_ad;
}

@Override
RecyclerView.ViewHolder viewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_ad, parent, false);
return new AdViewHolder(view);
}

@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
try {
AdViewHolder adViewHolder = (AdViewHolder) holder;
AdVo ad = (AdVo) item;
adViewHolder.bind(ad);
} catch (ClassCastException e) {
L. printStackTrace(e);
}
}
},
USER {
@Override
boolean is(Object item) {
return item instanceof UserVo;
}

@Override
int type() {
return R. layout.cell_user;
}

@Override
RecyclerView.ViewHolder viewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_user, parent, false);
return new UserViewHolder(view);
}

@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
try {
UserViewHolder userViewHolder = (UserViewHolder) holder;
UserVo user = (UserVo) item;
userViewHolder.bind(user);
} catch (ClassCastException e) {
L. printStackTrace(e);
}
}
};

static CellType get(Object item) {
for (CellType cellType : CellType.values()) {
if (cellType.is(item)) {
return cellType;
}
}
throw new NoSuchRecyclerItemTypeException();
}

static CellType get(int viewType) {
for (CellType cellType : CellType.values()) {
if (cellType.type() == viewType) {
return cellType;
}
}
throw new NoSuchRecyclerViewTypeException();
}

abstract boolean is(Object item);

abstract int type();

abstract RecyclerView.ViewHolder viewHolder(ViewGroup parent);

abstract void bind(RecyclerView.ViewHolder holder, Object item);
}

protected static class ProgressViewHolder extends RecyclerView.ViewHolder {

public ProgressViewHolder(View itemView) {
super(itemView);
}
}

protected static class AdViewHolder extends RecyclerView.ViewHolder {

public AdViewHolder(View itemView) {
super(itemView);
}

public void bind(AdVo ad) {
// Bind ad...
}
}

protected static class UserViewHolder extends RecyclerView.ViewHolder {

public UserViewHolder(View itemView) {
super(itemView);
}

public void bind(UserVo user) {
// Bind user...
}
}
}


Ще трохи більше «чистоти»
В якості альтернативи перевірку на тип можна замінити ще одним «табличним методом». Для перевірки відповідності типів можна використовувати колекцію map.

Прибираємо метод is() і ініціалізуємо відповідну колекцію map:

private enum CellType {
...
static Map<Class, CellType> typeTable = new HashMap<>();

static {
typeTable.put(ProgressVo.class, PROGRESS);
typeTable.put(AdVo.class AD);
typeTable.put(UserVo.class USER);
}

static CellType get(Object item) {
return typeTable.get(item.getClass());
}
...
}

Даний підхід варто розглядати саме як альтернативний. Тобто це така напівзахід (вирішили питання з перевіркою на тип, але не торкнулися перетворення типів), яка до того ж спрощує контракт enum.

Чим це загрожує?

А тим що можна в гарячці бою внесення змін дуже легко забути про це typeTable і отримати NPE.

Підтримуючи ж «повний» контракт (мова йде про метод is()) така ситуація виключена.

Висновок
Отже, що ми отримали на виході?

Почали з цього:

Ugly Adapter
private static final int TYPE_PROGRESS = 10;
private static final int TYPE_AD = 20;
private static final int TYPE_USER = 30;

@Override
public int getItemViewType(int position) {
Object item = itemList.get(position);
if (item instanceof ProgressVo) {
return TYPE_PROGRESS;
} else if (item instanceof AdVo) {
return TYPE_AD;
} else if (item instanceof UserVo) {
return TYPE_USER;
} else {
throw new NoSuchRecyclerItemTypeException();
}
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_PROGRESS) {
View view = inflater.inflate(R. layout.cell_progress, parent, false);
return new UsersRecyclerAdapter.ProgressViewHolder(view);
} else if (viewType == TYPE_AD) {
View view = inflater.inflate(R. layout.cell_ad, parent, false);
return new UsersRecyclerAdapter.AdViewHolder(view);
} else if (viewType == TYPE_USER) {
View view = inflater.inflate(R. layout.cell_user, parent, false);
return new UsersRecyclerAdapter.UserViewHolder(view);
} else {
throw new NoSuchRecyclerViewTypeException();
}
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ProgressViewHolder) {
// Do nothing.
} else if (holder instanceof AdViewHolder) {
((AdViewHolder) holder).bind((AdVo) itemList.get(position));
} else if (holder instanceof UserViewHolder) {
((UserViewHolder) holder).bind((UserVo) itemList.get(position));
}
}


І прийшли до цього:

Clean Adapter
@Override
public int getItemViewType(int position) {
return CellType.get(itemList.get(position)).type();
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return CellType.get(viewType).holder(parent);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Object item = itemList.get(position);
CellType.get(item).bind(holder, item);
}

private enum CellType {
PROGRESS {
@Override
boolean is(Object item) {
return item instanceof ProgressVo;
}

@Override
int type() {
return R. layout.cell_progress;
}

@Override
RecyclerView.ViewHolder holder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_progress, parent, false);
return new ProgressViewHolder(view);
}

@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
// Do nothing.
}
},
AD {
@Override
boolean is(Object item) {
return item instanceof AdVo;
}

@Override
int type() {
return R. layout.cell_ad;
}

@Override
RecyclerView.ViewHolder holder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_ad, parent, false);
return new AdViewHolder(view);
}

@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
try {
AdViewHolder adViewHolder = (AdViewHolder) holder;
AdVo ad = (AdVo) item;
adViewHolder.bind(ad);
} catch (ClassCastException e) {
L. printStackTrace(e);
}
}
},
USER {
@Override
boolean is(Object item) {
return item instanceof UserVo;
}

@Override
int type() {
return R. layout.cell_user;
}

@Override
RecyclerView.ViewHolder holder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R. layout.cell_user, parent, false);
return new UserViewHolder(view);
}

@Override
void bind(RecyclerView.ViewHolder holder, Object item) {
try {
UserViewHolder userViewHolder = (UserViewHolder) holder;
UserVo user = (UserVo) item;
userViewHolder.bind(user);
} catch (ClassCastException e) {
L. printStackTrace(e);
}
}
};

static CellType get(Object item) {
for (CellType cellType : CellType.values()) {
if (cellType.is(item)) {
return cellType;
}
}
throw new NoSuchRecyclerItemTypeException();
}

static CellType get(int viewType) {
for (CellType cellType : CellType.values()) {
if (cellType.type() == viewType) {
return cellType;
}
}
throw new NoSuchRecyclerViewTypeException();
}

abstract boolean is(Object item);

abstract int type();

abstract RecyclerView.ViewHolder holder(ViewGroup parent);

abstract void bind(RecyclerView.ViewHolder holder, Object item);
}


Пройдемося по позначених на початку статті питань.

  • полегшити зміна і масштабування типів комірок
  • мінімізувати кількість місць для зміни, знизивши ризик потенційних помилок
У нас є рівно одне місце як для внесення змін існуючих осередків (під стрімко мінливі бажання клієнта) так і для додавання нових. Причому, при додаванні нового типу комірки виключено, що ми щось забудемо, т. к. зобов'язані підтримувати контракт поточного enum. Просто і безпечно.

  • позбутися if-else каліцтва
Громіздкою і потворної логіки вибору більше немає. Немає і трьох місць де ця логіка використовувалася. Ризик помилки у зв'язку з цим виключений. Та й колеги тепер не засміють.

  • позбутися потворних перевірок на тип і небезпечних привидів типів
Це питання в рамках даної статті частково залишився відкритим. Як кажуть, не все відразу.

Три з половиною з чотирьох поставлених питань вирішені. Так чому я запропонував цей спосіб, якщо він не вирішує всіх питань?

Ну по-перше, інформації на одну статтю і так вдосталь.

По-друге, даний підхід вирішує більшість питань і спрощує розробку і підтримку коду.

І по-третє, цей спосіб дуже простий, лаконічний і швидкий. Тобто чаша ваг робота — вигода тут однозначно на стороні вигоди. А вигоду отримуємо чималу.

Так, тема виглядає дещо холиварной неоднозначною і може породити суперечки. Але оскільки списки займають чи не ключову роль в мобільних додатках, а Android SDK не надає красивого способу роботи з різнотипними осередками з коробки, то я вважав за потрібне поділитися одним з непоганих способів вирішення даної проблеми.

До зустрічі у другій частині, де ми поговоримо про те, як можна позбутися від перевірок на тип і приведення типів.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.