Добавить в цитаты Настройки чтения

Страница 281 из 371



А теперь спроектируем аналогичный класс Account1, отличающийся только тем, что у него будет меньше полей. Вместо поля balance в классе появится соответствующая функция с этим же именем, вместо полей sum и result появятся аргументы у методов, обеспечивающие необходимую передачу информации. Вот как выглядит этот класс:

/// <summary>

/// Класс Account1 определяет банковский счет.

/// Вариант с аргументами и функциями

/// </summary>

public class Account1

{

    //закрытые поля класса

    int debit=0, credit=0;

    /// <summary>

    /// Зачисление на счет с проверкой

    /// </summary>

    /// <param name="sum">зачисляемая сумма</param>

    public void putMoney(int sum)

    {

        int res =1;

        if (sum >0)credit += sum;

        else res = -1;

        Mes(res,sum);

     }//putMoney

     /// <summary>

     /// Снятие со счета с проверкой

     /// </summary>

     /// <param name="sum"> снимаемая сумма</param>

     public void getMoney(int sum)

     {

          int res=2;

          if(sum <= balance())debit += sum;

          else res = -2;

          balance();

          Mes(res, sum);

     }//getMoney

/// <summary>

/// вычисление баланса

/// </summary>

/// <returns>тeкyщий бaлaнc</returns>

     int balance()

     {

          return(credit — debit);

     }

     /// <summary>

     /// Уведомление о выполнении операции

     /// </summary>

     void Mes(int result, int sum)

     {

          switch (result)

     {

              case 1:

                  Console.WriteLine("Операция зачисления денег прошла успешно!");

                  Console.WriteLine("Сумма={0},

                       Ваш текущий баланс={1}", sum,balance());

                  break;

             case 2:

                  Console.WriteLine("Операция снятия денег прошла успешно!");

                  Console.WriteLine("Сумма={0},

                        Ваш текущий баланс={1}", sum,balance());

                   break;

              case -1:

                   Console.WriteLine("Операция зачисления денег не выполнена!");

                   Console.WriteLine("Сумма должна быть больше нуля!");

                   Console.WriteLine("Сумма={0},





                         Ваш текущий баланс={1}", sum,balance());

                   break;

             case -2:

                   Console.WriteLine("Операция снятия денег не выполнена!");

                   Console.WriteLine("Сумма должна быть не больше баланса!");

                   Console.WriteLine("Сумма={0},

                          Ваш текущий баланс={1}", sum,balance());

                    break;

             default:

                   Console.WriteLine("Неизвестная операция!");

                  break;

          }

     }

}//Account1

Сравнивая этот класс с классом 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("Новый класс и новый счет!");

     Accountl myAccount1 = new Account1();

     myAccount1.putMoney(6000);

     myAccount1.getMoney(2500);

     myAccount1.putMoney(1000);

     myAccount1.getMoney(4000);

     myAccount1.getMoney(1000);

}

На рис. 9.1 показаны результаты работы этой процедуры.

Рис. 9.1. Тестирование классов Account и Account1

Функции с побочным эффектом

Функция называется функцией с побочным эффектом, если помимо результата, вычисляемого функцией и возвращаемого ей в операторе return, она имеет выходные аргументы с ключевыми словами ref и out. В языках C/C++ функции с побочным эффектом применяются сплошь и рядом. Хороший стиль ОО-программирования не рекомендует использование таких функций. Выражения, использующие функции с побочным эффектом, могут потерять свои прекрасные свойства, присущие им в математике. Если f(а) — функция с побочным эффектом, то a+f(а) может быть не равно f(а)+а, так что теряется коммутативность операции сложения.

Примером такой функции является функция f, приведенная выше. Вот тест, демонстрирующий потерю коммутативности сложения при работе с этой функцией-.

/// <summary>

/// тестирование побочного эффекта

/// </summary>

public void TestSideEffect()

{

    int a = 0, b=0, c=0;

    a =1; b = a + f(ref a);

    a =1; с = f(ref a)+ a;

    Console.WriteLine("a={0}, b={1}, c={2}",a,b,c);

}

На рис. 9.2 показаны результаты работы этого метода.

Рис. 9.2. Демонстрация вызова функции с побочным эффектом

Обратите внимание на полезность указания ключевого слова ref в момент вызова. Его появление хоть как-то оправдывает не коммутативность сложения.

Методы. Перегрузка

Должно ли быть уникальным имя метода в классе? Нет, этого не требуется. Более того, проектирование методов с одним и тем же именем является частью стиля программирования на C++ и стиля С#.

Существование в классе методов с одним и тем же именем называется перегрузкой, а сами одноименные методы называются перегруженными.

Перегрузка методов полезна, когда требуется решать подобные задачи с разным набором аргументов. Типичный пример — это нахождение площади треугольника. Площадь можно вычислить потрем сторонам, по двум углам и стороне, по двум сторонам и углу между ними и при многих других наборах аргументов. Считается удобным во всех случаях иметь для метода одно имя, например Square, и всегда, когда нужно вычислить площадь, не задумываясь, вызывать метод Square, передавая ему известные в данный момент аргументы.

Перегрузка характерна и для знаков операций. В зависимости от типов аргументов, один и тот же знак может выполнять фактически разные операции. Классическим примером является знак операции сложения +, который играет роль операции сложения не только для арифметических данных разных типов, но и выполняет конкатенацию строк.

О перегрузке операций при определении класса будет подробно сказано в лекции, посвященной классам.

Перегрузка требует уточнения семантики вызова метода. Когда встречается вызов неперегруженного метода, то имя метода в вызове однозначно определяет, тело какого метода должно выполняться в точке вызова. Когда же метод перегружен, то знания имени недостаточно — оно не уникально. Уникальной характеристикой перегруженных методов является их сигнатура. Перегруженные методы, имея одинаковое имя, должны отличаться либо числом аргументов, либо их типами, либо ключевыми словами (заметьте: с точки зрения сигнатуры, ключевые слова ref и out не отличаются). Уникальность сигнатуры позволяет вызвать требуемый перегруженный метод.