Пример: класс Account. Помимо двух основных полей credit и debit, хранящих приход и расход счета, введем поле balance, которое задает текущее состояние счета, и два поля, связанных с последней выполняемой операцией. Поле sum будет хранить сумму денег текущей операции, а поле result - результат выполнения операции.
Поля класса или функции без аргументов?
Почему у методов класса мало аргументов?
Все дело в том, что методы класса - это не просто процедуры; это процедуры, обслуживающие данные. Все поля доступны любому методу по определению. Нужно четко понимать, что в момент выполнения программной системы работа идет не с классом, а с объектами - экземплярами класса. Из полей соответствующего объекта - цели вызова - извлекается информация, нужная методу в момент вызова, а работа метода чаще всего сводится к обновлению значений полей этого объекта. Поэтому очевидно, что методу не нужно через входные аргументы передавать информацию, содержащуюся в полях. Если в результате работы метода обновляется значение некоторого поля, то, опять-таки, не нужен никакой выходной аргумент.
Поля хранят информацию о состоянии объектов класса. Состояние объекта динамически изменяется в ходе вычислений - обновляются значения полей. Часто возникающая дилемма при проектировании класса: что лучше - создать ли поле, хранящее информацию, или создать функцию без аргументов, вычисляющую значение этого поля всякий раз, когда это значение понадобится. Решение дилеммы - это вечный для программистов выбор между памятью и временем. Если предпочесть поле, то это приводит к дополнительным расходам памяти. Они могут быть значительными, когда создается большое число объектов - ведь свое поле должен иметь каждый объект. Если предпочесть функцию, то это потребует временных затрат на вычисление значения, и затраты могут быть значительными в сравнении с выбором текущего значения поля.
// Класс Account определяет банковский счет, с возможностью
// трех операций: положить деньги на счет, снять со счета,
// узнать баланс. Вариант с полями
public class Account {
//закрытые поля класса
int debit=0, credit=0, balance =0;
int sum =0, result=0;
public void putMoney(int sum) { // Зачисление на счет
this.sum = sum;
if (sum >0) {
credit += sum; balance = credit - debit; result =1;
}
else result = -1;
Mes();
}
public void getMoney(int sum) {// Снятие со счета
this.sum = sum;
if(sum <= balance) {
debit += sum; balance = credit - debit; result =2;
}
else result = -2;
Mes();
}
void Mes() {// Уведомление о выполнении операции
switch (result) {
case 1:
Console.WriteLine("Операция зачисления прошла успешно!");
Console.WriteLine("Cумма={0},
Ваш текущий баланс={1}",sum, balance);
break;
case 2:
Console.WriteLine("Операция снятия денег
прошла успешно!");
Console.WriteLine("Cумма={0},
Ваш текущий баланс={1}", sum,balance);
break;
case -1:
Console.WriteLine("Операция зачисления не выполнена!");
Console.WriteLine("Сумма должна быть больше нуля!");
Console.WriteLine("Cумма={0},
Ваш текущий баланс={1}", sum,balance);
break;
case -2:
Console.WriteLine("Операция снятия не выполнена!");
Console.WriteLine("Сумма должна быть
не больше баланса!");
Console.WriteLine("Cумма={0},
Ваш текущий баланс={1}", sum,balance);
break;
default:
Console.WriteLine("Неизвестная операция!");
break;
} }}
Как можно видеть, у методов getMoney и putMoney имеется один входной аргумент. Это тот аргумент, который нужен, поскольку только клиент может решить, какую сумму он хочет снять или положить на счет. Других аргументов у методов класса нет - вся информация передается через поля класса. Уменьшение числа аргументов приводит к повышению эффективности работы с методами, так как исчезают затраты на передачу фактических аргументов. Но за все надо платить. В данном случае, усложняются сами операции работы со вкладом, поскольку нужно в момент выполнения операции обновлять значения многих полей класса. Закрытый метод Mes вызывается после выполнения каждой операции, сообщая о том, как прошла операция, и информируя клиента о текущем состоянии его баланса.
Теперь спроектируем аналогичный класс Account1, отличающийся только тем, что у него будет меньше полей. Вместо поля balance в классе появится соответствующая функция с этим же именем, вместо полей sum и result появятся аргументы у методов, обеспечивающие необходимую передачу информации. Вот как выглядит этот класс:
/// Класс Account1 определяет банковский счет.
/// Вариант с аргументами и функциями
public class Account1{
//закрытые поля класса
int debit=0, credit=0;
public void putMoney(int sum) {// Зачисление на счет
int res =1;
if (sum >0)credit += sum;
else res = -1;
Mes(res,sum);
}
public void getMoney(int sum) {// Снятие со счета
int res=2;
if(sum <= balance())debit += sum;
else res = -2;
balance();
Mes(res, sum);
}
int balance() { // вычисление баланса
return(credit - debit);
}
// Уведомление о выполнении операции
void Mes(int result, int sum) {
switch (result) {
case 1:
Console.WriteLine("Операция зачисления прошла успешно!");
Console.WriteLine("Cумма={0},
Ваш текущий баланс={1}", sum,balance());
break;
case 2:
Console.WriteLine("Операция снятия прошла успешно!");
Console.WriteLine("Cумма={0},
Ваш текущий баланс={1}", sum,balance());
break;
case -1:
Console.WriteLine("Операция зачисления не выполнена!");
Console.WriteLine("Сумма должна быть больше нуля!");
Console.WriteLine("Cумма={0},
Ваш текущий баланс={1}", sum,balance());
break;
case -2:
Console.WriteLine("Операция снятия не выполнена!");
Console.WriteLine("Сумма должна быть
не больше баланса!");
Console.WriteLine("Cумма={0},
Ваш текущий баланс={1}", sum,balance());
break;
default:
Console.WriteLine("Неизвестная операция!");
break;
} }}
Сравнивая этот класс с пердыдущим классом Account, можно видеть, что число полей сократилось с пяти до двух, упростились основные методы getMoney и putMoney. Но, в качестве платы, у класса появился дополнительный метод balance(), многократно вызываемый, и у метода Mes теперь появились два аргумента.
Поцедура класса Testing, тестирующую работу с классами Account и Account1:
public void TestAccounts() {
Account myAccount = new Account();
myAccount.putMoney(6000);
myAccount.getMoney(2500);
myAccount.putMoney(1000);
myAccount.getMoney(4000);
myAccount.getMoney(1000);
//Аналогичная работа с классом Account1
Console.WriteLine("Новый класс и новый счет!");
Account1 myAccount1 = new Account1();
myAccount1.putMoney(6000);
myAccount1.getMoney(2500);
myAccount1.putMoney(1000);
myAccount1.getMoney(4000);
myAccount1.getMoney(1000);
}
Функция называется функцией с побочным эффектом, если помимо результата, вычисляемого функцией и возвращаемого ей в операторе return, она имеет выходные аргументы с ключевыми словами ref и out. В языках C/C++ функции с побочным эффектом применяются сплошь и рядом. Хороший стиль ОО-программирования не рекомендует использование таких функций. Выражения, использующие функции с побочным эффектом, могут потерять свои прекрасные свойства, присущие им в математике. Если f(a) - функция с побочным эффектом, то a+f(a) может быть не равно f(a) +a, так что теряется коммутативность операции сложения.
public void TestSideEffect() { // тестирование побочного эффекта
int a = 0, b=0, c=0;
a =1; b = a + f(ref a);
a =1; c = f(ref a)+ a;
Console.WriteLine("a={0}, b={1}, c={2}",a,b,c);
}
Результаты работы этого метода:
a=2, b=2, c=3;
Обратите внимание на полезность указания ключевого слова ref в момент вызова. Его появление оправдывает некоммутативность сложения.