Тверской государственный университет
Опубликован: 22.11.2005 | Доступ: свободный | Студентов: 30261 / 1762 | Оценка: 4.31 / 3.69 | Длительность: 28:26:00
ISBN: 978-5-9556-0050-5
Лекция 22:

Универсальность. Классы с родовыми параметрами

Как справиться с арифметикой

Представьте себе, что мы хотим иметь специализированный вариант нашего списка, элементы которого допускали бы операцию сложения и одно из полей которого сохраняло бы сумму всех элементов, добавленных в список. Как задать соответствующее ограничение на класс?

Как уже говорилось, наличие ограничения операции, где можно было бы указать, что над элементами определена операция +, решало бы проблему. Но такого типа ограничений нет. Хуже того, нет и интерфейса INumeric, аналогичного IComparable, определяющего метод сложения Add. Так что нам не может помочь и ограничение наследования.

Вот один из возможных выходов, предлагаемых в такой ситуации. Стратегия следующая: определим абстрактный универсальный класс Calc с методами, выполняющими вычисления. Затем создадим конкретизированных потомков этого класса. В классе, задающем список с суммированием, введем поле класса Calc. При создании экземпляров класса будем передавать фактические типы ключа и элементов, а также соответствующий калькулятор, но уже не как тип, а как аргумент конструктора класса. Этот калькулятор, согласованный с типом элементов, и будет выполнять нужные вычисления. Давайте приступим к реализации этой стратегии. Начнем с определения класса Calc:

public abstract class Calc<T>
{
	public abstract T Add(T a, T b);
	public abstract T Sub(T a, T b);
	public abstract T Mult(T a, T b);
	public abstract T Div(T a, T b);
}

Наш абстрактный универсальный класс определяет четыре арифметические операции. Давайте построим трех его конкретизированных потомков:

public class IntCalc : Calc<int>
{
	public override int Add(int a, int b) { return (a + b);}
	public override int Sub(int a, int b) { return (a - b);}
	public override int Mult(int a, int b) { return (a * b);}
	public override int Div(int a, int b) { return (a / b); }
}
public class DoubleCalc : Calc<double>
{
	public override double Add(double a, double b) 
		{return (a + b);}
	public override double Sub(double a, double b) 
		{return (a - b);}
	public override double Mult(double a, double b) 
		{return (a * b);}
	public override double Div(double a, double b) 
		{return (a / b);}
}
public class StringCalc : Calc<string>
{
	public override string Add(string a, string b) 
		{return (a + b);}
	public override string Sub(string a, string b) 
		{return (a );}
	public override string Mult(string a, string b) 
		{return (a );}
	public override string Div(string a, string b) 
		{return (a);}
}

Здесь определяются три разных калькулятора: один - над целочисленными данными, другой - над данными с плавающей точкой, третий - над строковыми данными. В последнем случае определена, по сути, только операция сложения строк (конкатенации).

Теперь нам нужно ввести изменения в ранее созданный класс OneLinkList. Обратите внимание на важный технологический принцип работы с объектными системами. Пусть уже есть нормально работающий класс с нормально работающими клиентами класса. Не следует изменять этот класс. Класс закрыт для изменений. Используйте наследование и открывайте класс-потомок, в который и вносите изменения, учитывающие добавляемую специфику класса. Принцип "Закрыт - Открыт" является одним из важнейших принципов построения программных систем в объектном стиле.

В полном соответствии с этим принципом построим класс SumList - потомок класса OneLinkList. То, что родительский класс является универсальным, ничуть не мешает строить потомка класса, сохраняющего универсальный характер родителя.

public class SumList<K, T> : OneLinkList<K, T> where K : 
	IComparable<K>
	
{
	Calc<T> calc;
	T sum;
	public SumList(Calc<T> calc)
	{ this.calc = calc; sum = default(T); }
	public new void add(K key, T item)
	{
		Node<K, T> newnode = new Node<K, T>();
		if (first == null)
		{
			first = newnode; cursor = newnode;
			newnode.key = key; newnode.item = item;
			sum = calc.Add(sum, item);
		}
		else
		{
			newnode.next = cursor.next; cursor.next = newnode;
			newnode.key = key; newnode.item = item;
			sum = calc.Add(sum, item);
		}
	}
	public T Sum()
	{return (sum); }
}//SumList

У класса добавилось поле sum, задающее сумму хранимых элементов, и поле calc - калькулятор, выполняющий вычисления. Метод add, объявленный в классе с модификатором new, скрывает родительский метод add, задавая собственную реализацию этого метода. Родительский метод можно было бы определить как виртуальный, переопределив его у потомка, но я не стал трогать код родительского класса. К классу добавился еще один запрос, возвращающий значение поля sum.

Некоторые изменения в уже существующем проекте пришлось-таки сделать, изменив статус доступа у полей. А все потому, что в целях экономии текста кода я не стал закрывать поля и вводить, как положено, открытые процедуры-свойства для закрытых полей.

Проведем теперь эксперименты с новыми вариантами списков, допускающих суммирование элементов:

public void TestSum()
{
	SumList<string, int> list1 = 
		new SumList<string, int>(new IntCalc());
	list1.add("Петр", 33); list1.add("Павел", 44);
	Console.WriteLine("sum= {0}", list1.Sum());
	SumList<string, double> list2 = 
		new SumList<string, double> (new DoubleCalc());
	list2.add("Петр", 33.33); list2.add("Павел", 44.44);
	Console.WriteLine("sum= {0}", list2.Sum());
	SumList<string, string> list3 = 
		new SumList<string, string> (new StringCalc());
	list3.add("Мама", " Мама мыла "); list3.add("Маша", 
		"Машу мылом!");
	Console.WriteLine("sum= {0}", list3.Sum());
}

Обратите внимание на создание списков:

SumList<string, int> list1 = 
		new SumList<string, int>(new IntCalc());
	SumList<string, double> list2 =
		new SumList<string, double>(new DoubleCalc());
	SumList<string, string> list3 =
		new SumList<string, string>(new StringCalc());

Как видите, конструктору объекта передается калькулятор, согласованный с типами данных, которые хранятся в списке. Результаты вычислений, полученных при работе с этими списками, приведены на рис. 22.6.

Списки с суммированием

Рис. 22.6. Списки с суммированием
Александр Галабудник
Александр Галабудник

Не обнаружил проекты, которые используются в примерах в лекции, также не увидел список задач.

Александра Гусева
Александра Гусева