bool a = SomeCondition();
/* Как упростить следующий код? */
bool b;
if( a.ToString().Length == 5 )
{
b = true;
} else
{
b = false;
}
Этот небольшой отрывок я привел отчасти ради шутки, а отчасти для того, что бы показать о чем идет речь. Разумеется, я не думаю, что кто-то реально так пишет. Но, в реальности наши задачи несколько сложнее и, по мере поступления новых вводных, мы часто перестаем видеть знакомые шаблоны и, как следствие, начинаем писать в стиле приведенного выше шуточного примера.Товары и услуги
Давайте рассмотрим менее очевидный и более приближенный к реальности пример. Заказчик попросил нас вывести на экран такую вот табличку.Все данные хранятся в базе данных. Однако, там только цифры. Товары в базе имеют специальные коды, а расшифровку этих кодов заказчик дал в бумажном виде. Вы уточняете требования заказчика и получаете ответы в стиле стандартного заказчика:
- Как часто будет меняться перечень товаров и услуг? Пока менять не планируют, но через несколько месяцев фирма будет расширяться, может добавиться еще пара-тройка таблиц, возможно добавятся строки к существующим.
- Куда выводить таблички - десктопное приложение, веб-странички, принтер? Пока только десктоп, потом, возможно понадобится принтер.
- Можно ли вносить изменения в базу данных? База данных уже работает с существующими системами - любые изменения будут перезабиты ближайшим слиянием данных.
public interface IDataViewer
{
void ShowGrid( string caption, IEnumerable<RowInfo> rows );
}
public class RowInfo
{
public string Title { get; set; }
public string Count { get; set; }
public string Comment { get; set; }
}
И пошел работать над реализацией для десктопа. В свою очередь, специалист по базам данных, разобравшись со структурой хранения, написал методы для работы с каждой таблицей:public interface IDataRetriever
{
int GetPurchases( string depotCode, int code, bool spentOnly );
int GetServices( string gangKey, int accessLevel, int code, bool paidOnly );
}
А что за дополнительные параметры мы тут видим? Пока это константы, а в дальнейшем будут определяться при входе в программу. Хорошо, осталось совсем немного - соединить все это в вместе.Решение "в лоб"
Первое, что приходит в голову, это самое что ни на есть простое решение - для каждой строки таблицы вызвать соответствующий метод, заполнить класс RowInfo, добавить его в список и, потом, весь список передать методу ShowGrid. Давайте посмотри, как это будет выглядеть:public void SillyFillGrid( IDataRetriever retriever, IDataViewer viewer )
{
var depotCode = GetDepotCode();
var gangKey = GetGangKey();
var accessLevel = GetAccessLevel();
var rows = new List<RowInfo>();
var row = new RowInfo();
var count1 = 0;
var count2 = 0;
row.Title = "Молотки";
count1 = retriever.GetPurchases( depotCode, 1, false );
row.Count = count1 > 0 ? count1.ToString() : "--";
count2 = count1 > 0 ? retriever.GetPurchases( depotCode, 1, true ) : 0;
row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : "";
rows.Add( row );
row.Title = "Гвозди";
count1 = retriever.GetPurchases( depotCode, 2, false );
row.Count = count1 > 0 ? count1.ToString() : "--";
count2 = count1 > 0 ? retriever.GetPurchases( depotCode, 2, true ) : 0;
row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : "";
rows.Add( row );
row.Title = "Электрошокер бытовой";
count1 = retriever.GetPurchases( depotCode, 3, false );
row.Count = count1 > 0 ? count1.ToString() : "--";
count2 = count1 > 0 ? retriever.GetPurchases( depotCode, 3, true ) : 0;
row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : "";
rows.Add( row );
row.Title = "Патроны к пистолету Макаров";
count1 = retriever.GetPurchases( depotCode, 4, false );
row.Count = count1 > 0 ? count1.ToString() : "--";
count2 = count1 > 0 ? retriever.GetPurchases( depotCode, 4, true ) : 0;
row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : "";
rows.Add( row );
viewer.ShowGrid( "Закуплено товаров", rows );
}
Фууух! Одну табличку заполнили. Теперь еще нужно написать то же самое для второй. Однако, уже по первой таблице мы видим множество повторяющихся операций. А что, если завтра у некоторых товаров сменятся коды. К примеру электрошокер станет не 3-м, а 18-м или 11-м. Нам придется выискивать в дебрях кода нужные цифры, а там каждая в двух экземплярах, что дает вероятность ошибиться - заменить в одном месте и забыть про второе.Упрощаем код
А что если вынести коды и названия в массив и "пробежать" по нему в цикле foreach.public void AverageFillGrid( IDataRetriever retriever, IDataViewer viewer )
{
var depotCode = GetDepotCode();
var gangKey = GetGangKey();
var accessLevel = GetAccessLevel();
var pInfo = new[]{
new{code = 1, title = "Молотки"},
new{code = 2, title = "Гвозди"},
new{code = 3, title = "Электрошокер бытовой"},
new{code = 4, title = "Патроны к пистолету Макаров"},
};
var sInfo = new[]{
new{code = 14, title = "Мелкие хулиганства"},
new{code = 22, title = "Беседа с пристрастием"},
new{code = 24, title = "Возврат долга"},
new{code = 31, title = "Последний разговор"},
};
var rows = new List<RowInfo>();
foreach( var p in pInfo )
{
var row = new RowInfo();
row.Title = p.title;
var count1 = retriever.GetPurchases( depotCode, p.code, false );
row.Count = count1 > 0 ? count1.ToString() : "--";
var count2 = count1 > 0 ? retriever.GetPurchases( depotCode, p.code, true ) : 0;
row.Comment = count2 > 0 ? string.Format( "из них использовано {0} шт", count2 ) : "";
rows.Add( row );
}
viewer.ShowGrid( "Закуплено товаров", rows );
rows = new List<RowInfo>();
foreach( var s in sInfo )
{
var row = new RowInfo();
row.Title = s.title;
var count1 = retriever.GetServices( gangKey, accessLevel, s.code, false );
row.Count = count1 > 0 ? count1.ToString() : "--";
var count2 = count1 > 0 ? retriever.GetServices( gangKey, accessLevel, s.code, true ) : 0;
row.Comment = count2 > 0 ? string.Format( "из них оплачено {0}", count2 ) : "";
rows.Add( row );
}
viewer.ShowGrid( "Оказано услуг", rows );
}
Как мы видим, в таком варианте примерно тот же объем кода покрывает уже две таблички. Кроме того, коды и названия представлены в читаемом виде и менять их будет гораздо проще.Доводим до конца
Однако, это еще не предел. Блоки кода по заполнению первой и второй табличек очень похожи, хотя в них и есть некоторые различия. А что, если вынести общую часть в отдельный метод.void FillGridHelper<T>( IDataViewer viewer, string caption, IEnumerable<T> data, Func<T, string> getTitle, Func<T, bool, int> getCount, string commentFormat )
{
viewer.ShowGrid( caption, data.Select( d =>
{
var row = new RowInfo { Title = getTitle( d ) };
var count1 = getCount( d, false );
row.Count = count1 > 0 ? count1.ToString() : "--";
var count2 = count1 > 0 ? getCount( d, true ) : 0;
row.Comment = count2 > 0 ? string.Format( commentFormat, count2 ) : "";
return row;
} ) );
}
У нас получилась функция с достаточно простым и относительно компактным кодом. При этом, все различия мы вынесли в параметры функции. Давайте посмотрим, как преобразится наш конечный метод с использованием данной вспомогательной функции:public void CleverFillGrid( IDataRetriever retriever, IDataViewer viewer )
{
var depotCode = GetDepotCode();
var gangKey = GetGangKey();
var accessLevel = GetAccessLevel();
var pInfo = new[]{
new{code = 1, title = "Молотки"},
new{code = 2, title = "Гвозди"},
new{code = 3, title = "Электрошокер бытовой"},
new{code = 4, title = "Патроны к пистолету Макаров"},
};
var sInfo = new[]{
new{code = 14, title = "Мелкие хулиганства"},
new{code = 22, title = "Беседа с пристрастием"},
new{code = 24, title = "Возврат долга"},
new{code = 31, title = "Последний разговор"},
};
FillGridHelper( viewer, "Закуплено товаров", pInfo, p => p.title, ( p, so ) => retriever.GetPurchases( depotCode, p.code, so ), "из них использовано {0} шт" );
FillGridHelper( viewer, "Оказано услуг", sInfo, s => s.title, ( s, po ) => retriever.GetServices( gangKey, accessLevel, s.code, po ), "из них оплачено {0}" );
}
Уже значительно симпатичнее и компактнее. Или, может быть можно еще упростить? ;)

Комментариев нет:
Отправить комментарий