Главная страница
Навигация по странице:

  • Объекты и структуры данных

  • Антисимметрия данных/объектов

  • Создание, анализ ирефакторинг


    Скачать 3.16 Mb.
    НазваниеСоздание, анализ ирефакторинг
    Дата29.09.2022
    Размер3.16 Mb.
    Формат файлаpdf
    Имя файлаChistyj_kod_-_Sozdanie_analiz_i_refaktoring_(2013).pdf
    ТипКнига
    #706087
    страница15 из 49
    1   ...   11   12   13   14   15   16   17   18   ...   49
    120
    Глава 5 . Форматирование
    листинг 5 .6 (продолжение)
    public double getMeanLineWidth() {
    return (double)totalChars/lineCount;
    }
    public int getMedianLineWidth() {
    Integer[] sortedWidths = getSortedWidths();
    int cumulativeLineCount = 0;
    for (int width : sortedWidths) {
    cumulativeLineCount += lineCountForWidth(width);
    if (cumulativeLineCount > lineCount/2)
    return width;
    }
    throw new Error("Cannot get here");
    }
    private int lineCountForWidth(int width) {
    return lineWidthHistogram.getLinesforWidth(width).size();
    }
    private Integer[] getSortedWidths() {
    Set widths = lineWidthHistogram.getWidths();
    Integer[] sortedWidths = (widths.toArray(new Integer[0]));
    Arrays.sort(sortedWidths);
    return sortedWidths;
    }
    }
    120

    Объекты и структуры
    данных
    Существует веская причина для ограничения доступа к переменным в програм- мах: мы не хотим, чтобы другие программисты зависели от них . Мы хотим иметь возможность свободно менять тип или реализацию этих переменных так, как считаем нужным . Тогда почему же многие программисты автоматически вклю- чают в свои объекты методы чтения/записи, предоставляя доступ к приватным переменным так, словно они являются открытыми?
    Абстракция данных
    Давайте сравним между собой листинги 6 .1 и 6 .2 . В обоих случаях код представ- ляет точку на декартовой плоскости . Однако в одном случае реализация открыта, а в другом она полностью скрыта от внешнего пользователя .
    6
    121

    122
    Глава 6 . Объекты и структуры данных
    листинг 6 .1 . Конкретная реализация Point public class Point {
    public double x;
    public double y;
    }
    листинг 6 .2 . Абстрактная реализация Point public interface Point {
    double getX();
    double getY();
    void setCartesian(double x, double y);
    double getR();
    double getTheta();
    void setPolar(double r, double theta);
    }
    Элегантность решения из листинга 6 .2 заключается в том, что внешний пользо- ватель не знает, какие координаты использованы в реализации — прямоугольные или полярные . А может, еще какие-нибудь! Тем не менее интерфейс безусловно напоминает структуру данных .
    Однако он представляет нечто большее, чем обычную структуру данных . Его методы устанавливают политику доступа к данным . Пользователь может читать значения координат независимо друг от друга, но присваивание координат долж- но выполняться одновременно, в режиме атомарной операции .
    С другой стороны, листинг 6 .1 явно реализован в прямоугольных координатах, а пользователь вынужден работать с этими координатами независимо . Более того, такое решение раскрывает реализацию даже в том случае, если бы переменные были объявлены приватными, и мы использовали одиночные методы чтения/записи .
    Скрытие реализации не сводится к созданию прослойки функций между пере- менными . Скрытие реализации направлено на формирование абстракций! Класс не просто ограничивает доступ к переменным через методы чтения/записи .
    Вместо этого он предоставляет абстрактные интерфейсы, посредством которых пользователь оперирует с сущностью данных . Знать, как эти данные реализованы, ему при этом не обязательно .
    Возьмем листинги 6 .3 и 6 .4 . В первом случае для получения информации о за- пасе топлива используются конкретные физические показатели, а во втором — абстрактные проценты . В первом, конкретном случае можно быть уверенным в том, что методы представляют собой обычные методы доступа к переменным .
    Во втором, абстрактном случае пользователь не имеет ни малейшего представ- ления о фактическом формате данных .
    листинг 6 .3 . Конкретная реализация Vehicle public interface Vehicle {
    double getFuelTankCapacityInGallons();
    double getGallonsOfGasoline();
    }
    122

    Антисимметрия данных/объектов
    123
    листинг 6 .4 . Абстрактная реализация Vehicle
    Abstract Vehicle public interface Vehicle {
    double getPercentFuelRemaining();
    }
    В обоих примерах вторая реализация является предпочтительной . Мы не хотим раскрывать подробности строения данных . Вместо этого желательно использо- вать представление данных на абстрактном уровне . Задача не решается простым использованием интерфейсов и/или методов чтения/записи . Чтобы найти луч- ший способ представления данных, содержащихся в объекте, необходимо серьез- но поразмыслить . Бездумное добавление методов чтения и записи — худший из всех возможных вариантов .
    Антисимметрия данных/объектов
    Два предыдущих примера показывают, чем объекты отличаются от структур дан- ных . Объекты скрывают свои данные за абстракциями и предоставляют функции, работающие с этими данными . Структуры данных раскрывают свои данные и не имеют осмысленных функций . А теперь еще раз перечитайте эти определения .
    Обратите внимание на то, как они дополняют друг друга, фактически являясь противоположностями . Различия могут показаться тривиальными, но они при- водят к далеко идущим последствиям .
    Возьмем процедурный пример из листинга 6 .5 . Класс
    Geometry работает с тремя классами геометрических фигур . Классы фигур представляют собой простые структуры данных, лишенные какого-либо поведения . Все поведение сосредо- точено в классе
    Geometry
    листинг 6 .5 . Процедурные фигуры public class Square {
    public Point topLeft;
    public double side;
    }
    public class Rectangle {
    public Point topLeft;
    public double height;
    public double width;
    }
    public class Circle {
    public Point center;
    public double radius;
    }
    продолжение
    123

    124
    Глава 6 . Объекты и структуры данных
    листинг 6 .5 (продолжение)
    public class Geometry {
    public final double PI = 3.141592653589793;
    public double area(Object shape) throws NoSuchShapeException
    {
    if (shape instanceof Square) {
    Square s = (Square)shape;
    return s.side * s.side;
    }
    else if (shape instanceof Rectangle) {
    Rectangle r = (Rectangle)shape;
    return r.height * r.width;
    }
    else if (shape instanceof Circle) {
    Circle c = (Circle)shape;
    return PI * c.radius * c.radius;
    }
    throw new NoSuchShapeException();
    }
    }
    Объектно-ориентированный программист недовольно поморщится и пожалуется на процедурную природу реализации — и будет прав . Но возможно, его пре- зрительная усмешка не обоснована . Подумайте, что произойдет при включении в
    Geometry функции perimeter()
    . Классы фигур остаются неизменными! И все остальные классы, зависящие от них, тоже остаются неизменными! С другой стороны, при добавлении новой разновидности фигур мне придется изменять все функции
    Geometry
    , чтобы они могли работать с ней . Перечитайте еще раз .
    Обратите внимание на то, что эти два условия диаметрально противоположны .
    Теперь рассмотрим объектно-ориентированное решение из листинга 6 .6 . Метод area()
    является полиморфным, класс
    Geometry становится лишним . Добавление новой фигуры не затрагивает ни одну из существующих функций, но при добав- лении новой функции приходится изменять все фигуры!
    1
    листинг 6 .6 . Полиморфные фигуры
    Polymorphic Shapes public class Square implements Shape {
    private Point topLeft;
    private double side;
    public double area() {
    return side*side;
    }
    }
    1
    У проблемы существуют обходные решения, хорошо известные опытным объектно- ориентированным программистам: например, паттерн ПОСЕТИТЕЛЬ или двойная дис- петчеризация . Но у этих приемов имеются собственные издержки, к тому же они обычно возвращают структуру к состоянию процедурной программы .
    124

    Антисимметрия данных/объектов
    125
    public class Rectangle implements Shape {
    private Point topLeft;
    private double height;
    private double width;
    public double area() {
    return height * width;
    }
    }
    public class Circle implements Shape {
    private Point center;
    private double radius;
    public final double PI = 3.141592653589793;
    public double area() {
    return PI * radius * radius;
    }
    }
    И снова мы наблюдаем взаимодополняющую природу этих двух определений .
    В этом проявляется основополагающая дихотомия между объектами и структу- рами данных .
    Процедурный код (код, использующий структуры данных) позволяет легко добав-
    лять новые функции без изменения существующих структур данных. Объектно-
    ориентированный код, напротив, упрощает добавление новых классов без изме-
    нения существующих функций.
    Обратные утверждения также истинны .
    Процедурный код усложняет добавление новых структур данных, потому что оно
    требует изменения всех функций. Объектно-ориентированный код усложняет
    добавление новых функций, потому что для этого должны измениться все классы.
    Таким образом, то, что сложно в ОО, просто в процедурном программировании, а то, что сложно в процедурном программировании, просто в ОО!
    В любой сложной системе возникают ситуации, когда вместо новых функций в систему требуется включить новые типы данных . Для таких ситуаций объекты и объектно-ориентированное программирование особенно уместны . Впрочем, бывает и обратное — вместо новых типов данных требуется добавить новые функции . Тогда лучше подходит процедурный код и структуры данных .
    Опытные программисты хорошо знают: представление о том, что все данные должны представляться в виде объектов — миф . Иногда предпочтительны про- стые структуры данных и процедуры, работающие с ними .
    125

    126
    Глава 6 . Объекты и структуры данных
    Закон Деметры
    Хорошо известное эвристическое правило, называемое законом Деметры
    1
    , гласит, что модуль не должен знать внутреннее устройство тех объектов, с которыми он работает . Как мы видели в предыдущем разделе, объекты скрывают свои данные и предоставляют операции для работы с ними . Это означает, что объект не дол- жен раскрывать свою внутреннюю структуру через методы доступа, потому что внутреннюю структуру следует скрывать .
    В более точной формулировке закон Деметры гласит, что метод f класса C должен ограничиваться вызовом методов следующих объектов:
    
    C;
    
    объекты, созданные f;
    
    объекты, переданные f в качестве аргумента;
    
    объекты, хранящиеся в переменной экземпляра C .
    Метод не должен вызывать методы объектов, возвращаемых любыми из раз- решенных функций . Другими словами, разговаривать можно с друзьями, но не с чужаками .
    Следующий код нарушает закон Деметры (среди прочего), потому что он вызы- вает функцию getScratchDir()
    для возвращаемого значения getOptions()
    , а затем вызывает getAbsolutePath()
    для возвращаемого значения getScratchDir()
    final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
    Крушение поезда
    Подобная структура кода часто называется «крушением поезда», потому что цепочки вызовов напоминают сцепленные вагоны поезда . Такие конструкции считаются проявлением небрежного стиля программирования и их следует из- бегать [G36] . Обычно цепочки лучше разделить в следующем виде:
    Options opts = ctxt.getOptions();
    File scratchDir = opts.getScratchDir();
    final String outputDir = scratchDir.getAbsolutePath();
    Нарушают ли эти два фрагмента закон
    Деметры? Несомненно, вмещающий модуль знает, что объект контекста ctxt содержит значения параметров, в число которых входит и временный каталог, обладающий абсолютным путем . Это довольно большой объем информации
    1
    http://en .wikipedia .org/wiki/Law_of_Demeter
    126

    Закон Деметры
    127
    для одной функции . Вызывающая функция должна знать, как перемещаться между множеством разных объектов .
    Нарушает ли этот код закон Деметры или нет? Все зависит от того, чем являются ctxt
    ,
    Options и
    ScratchDir
    — объектами или структурами данных . Если это объ- екты, то их внутренняя структура должна скрываться, поэтому необходимость ин формации об их строении является явным нарушением закона Деметры .
    С другой стороны, если ctxt
    ,
    Options и
    ScratchDir представляют собой обычные структуры данных, не обладающие поведением, то они естественным образом раскрывают свою внутреннюю структуру, а закон Деметры на них не распро- страняется .
    Применение функций доступа затрудняет ситуацию . Если бы код был записан следующим образом, вероятно, у нас не возникало бы вопросов по поводу нару- шения закона Деметры:
    final String outputDir = ctxt.options.scratchDir.absolutePath;
    Ситуация существенно упростилась бы, если бы структуры данных просто со- держали открытые переменные без функций, а объекты — приватные переменные с открытыми функциями . Однако некоторые существующие инфраструктуры и стандарты (например, Beans) требуют, чтобы даже простые структуры данных имели методы чтения и записи .
    Гибриды
    Вся эта неразбериха иногда приводит к появлению гибридных структур — напо- ловину объектов, наполовину структур данных . Гибриды содержат как функции для выполнения важных операций, так и открытые переменные или открытые методы чтения/записи, которые во всех отношениях делают приватные перемен- ные открытыми . Другим внешним функциям предлагается использовать эти пе- ременные так, как в процедурных программах используются структуры данных
    1
    Подобные гибриды усложняют как добавление новых функций, так и новых структур данных . Они объединяют все худшее из обеих категорий . Не исполь- зуйте гибриды . Они являются признаком сумбурного проектирования, авторы которого не уверены (или еще хуже, не знают), что они собираются защищать: функции или типы .
    Скрытие структуры
    А если ctxt
    , options и scratchDir представляют собой объекты с реальным пове- дением? Поскольку объекты должны скрывать свою внутреннюю структуру, мы не сможем перемещаться между ними . Как же в этом случае узнать абсолютный путь временного каталога?
    1
    Иногда это называется «функциональной завистью» (Feature Envy) — из [Refactoring] .
    127

    128
    Глава 6 . Объекты и структуры данных ctxt.getAbsolutePathOfScratchDirectoryOption();
    или ctx.getScratchDirectoryOption().getAbsolutePath()
    Первый вариант приведет к разрастанию набора методов объекта ctxt
    . Второй вариант предполагает, что getScratchDirectoryOption()
    возвращает структуру данных, а не объект . Ни один из вариантов не вызывает энтузиазма .
    Если ctxt является объектом, то мы должны приказать ему выполнить некую опе- рацию, а не запрашивать у него информацию о его внутреннем устройстве . За- чем нам понадобился абсолютный путь к временному каталогу? Что мы собира- емся с ним делать? Рассмотрим следующий фрагмент того же модуля (располо- женный на много строк ниже):
    String outFile = outputDir + "/" + className.replace('.', '/') + ".class";
    FileOutputStream fout = new FileOutputStream(outFile);
    BufferedOutputStream bos = new BufferedOutputStream(fout);
    Смешение разных уровней детализации [G34][G6] выглядит немного пугающе .
    Точки, косые черты, расширения файлов и объекты
    File не должны так беспечно перемешиваться между собой и с окружающим кодом . Но если не обращать на это внимания, мы видим, что абсолютный путь временного каталога определялся для создания временного файла с заданным именем .
    Так почему бы не приказать объекту ctxt выполнить эту операцию?
    BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
    Выглядит вполне разумно! Такое решение позволяет объекту ctxt скрыть свое внутреннее строение, а текущей функции не приходится нарушать закон Деме- тры, перемещаясь между объектами, о которых ей знать не положено .
    Объекты передачи данных
    Квинтэссенцией структуры данных является класс с открытыми переменными и без функций . Иногда такие структуры называются объектами передачи данных, или DTO (Data Transfer Object) . Структуры DTO чрезвычайно полезны, особен- но при работе с базами данных, разборе сообщений из сокетов и т . д . С них часто начинается серия фаз преобразования низкоуровневых данных, полученных из базы, в объекты кода приложения .
    Несколько большее распространение получила форма bean-компонентов, пред- ставленная в листинге 6 .7 . Bean-компоненты состоят из приватных переменных, операции с которыми осуществляются при помощи методов чтения/записи .
    Подобная форма псевдоинкапсуляции поднимает настроение некоторым блю- стителям чистоты ОО, но обычно не имеет других преимуществ .
    листинг 6 .7 . address.java public class Address {
    private String street;
    private String streetExtra;
    128

    Закон Деметры
    129
    private String city;
    private String state;
    private String zip;
    public Address(String street, String streetExtra,
    String city, String state, String zip) {
    this.street = street;
    this.streetExtra = streetExtra;
    this.city = city;
    this.state = state;
    this.zip = zip;
    }
    public String getStreet() {
    return street;
    }
    public String getStreetExtra() {
    return streetExtra;
    }
    public String getCity() {
    return city;
    }
    public String getState() {
    return state;
    }
    public String getZip() {
    return zip;
    }
    }
    Активные записи
    Активные записи (Active Records) составляют особую разновидность DTO . Они тоже представляют собой структуры данных с открытыми переменными (или пе- ременными с bean-доступом), но обычно в них присутствуют навигационные ме- тоды — такие, как save или find
    . Активные записи чаще всего являются результа- тами прямого преобразования таблиц баз данных или других источников данных .
    К сожалению, разработчики часто пытаются интерпретировать такие структуры данных, как объекты, и включают в них методы, реализующие бизнес-логику .
    Однако такой подход нежелателен, так как он создает гибрид между структурой данных и объектом .
    Конечно, проблема решается иначе: активные записи интерпретируются как структуры данных, а в программе создаются отдельные объекты, которые со- держат бизнес-логику и скрывают свои внутренние данные (которые, возможно, представляют собой обычные экземпляры класса активной записи) .
    129

    1   ...   11   12   13   14   15   16   17   18   ...   49


    написать администратору сайта