Programming Taskbook


E-mail:

Пароль:

Регистрация пользователя   Восстановление пароля

 

ЮФУ SMBU

Электронный задачник по программированию

©  М. Э. Абрамян (Южный федеральный университет, Университет МГУ-ППИ в Шэньчжэне), 1998–2025

 

PT for OOP | Выполнение задания OOP1Creat1 | Решение на языках Ruby, Java и Julia

Prev


Решения на языках Ruby, Java и Julia

Завершая рассмотрение задания OOP1Creat1, приведем его решения еще на трех языках, для которых предусмотрено создание развернутых заготовок в версии 1.1 задачника PT for OOP: Ruby, Java и Julia. Язык Ruby имеет много общего с языком Python; это тоже язык с динамической типизацией, в котором не требуется описывать полностью абстрактные классы и абстрактные методы. Язык Javа имеет объектную модель, близкую к модели языка C#. В ней объекты тоже всегда размещаются в динамической памяти и автоматически освобождают ее благодаря наличию сборщика мусора. Кроме того, в языке Java все методы классов по умолчанию являются виртуальными, поэтому модификатор virtual для них указывать не требуется (хотя можно использовать модификатор @Override, который, подобно аналогичному модификатору для языка C++, не является обязательным, но делает программу более наглядной). Язык Julia имеет объектную модель, которая существенно отличается от моделей всех рассмотренных ранее языков, что делает задачу реализации различных паттернов на данном языке особенно интересной.

Заметим, что программы, выполняющие задания на языках Ruby и Java, тоже можно разрабатывать в среде Visual Studio Code. Кроме этой среды задачник поддерживает среду RubyMine для языка Ruby и среду Eclipse для языка Java.

Вначале приведем заготовку программы для языка Ruby. Сравнивая ее с ранее приведенной заготовкой для языка Python, нетрудно заметить, насколько близки эти языки.

[Ruby]

# coding: utf-8
require "./PT"
class ConcreteProduct1
    def initialize(info)
        # Implement the constructor
    end

    def getInfo
        # Implement the method
    end

    def transform
        # Implement the method
    end
end

# Implement the ConcreteProduct2 class

class Creator
    def anOperation(info)
        p = factoryMethod(info)
        p.transform
        p.transform
        p.getInfo
    end
end

class ConcreteCreator1 < Creator
    def factoryMethod(info)
        # Implement the method
    end
end

# Implement the ConcreteCreator2 descendant class;
#   the anOperation method should not be
#   overridden in this class

def solve()
    task "OOP1Creat1"

end

start_solve

Следует обратить внимание на одну особенность Ruby: при достижения конца функции из нее возвращается последнее вычисленное значение; таким образом, в большинстве случаев слово return можно не использовать (в отличие от языка Python, где слово return должно обязательно указываться перед возвращаемым значением функции).

Теперь приведем решение задачи на языке Ruby.

[Ruby]

# coding: utf-8
require "./PT"
class ConcreteProduct1
    def initialize(info)
        @info = info.downcase
    end

    def getInfo
        @info
    end

    def transform
        (@info.length-2).downto(0) do |i|
            if @info[i] != " "
                @info.insert(i+1, " ")
            end
        end
    end
end

class ConcreteProduct2
    def initialize(info)
        @info = info.upcase
    end

    def getInfo
        @info
    end

    def transform
        (@info.length-2).downto(0) do |i|
            if @info[i] != "*"
                @info.insert(i+1, "**")
            end
        end
    end
end

class Creator
    def anOperation(info)
        p = factoryMethod(info)
        p.transform
        p.transform
        p.getInfo
    end
end

class ConcreteCreator1 < Creator
    def factoryMethod(info)
        ConcreteProduct1.new(info)
    end
end

class ConcreteCreator2 < Creator
    def factoryMethod(info)
        return ConcreteProduct2.new(info)
    end
end

def solve()
    task "OOP1Creat1"
    c1 = ConcreteCreator1.new
    c2 = ConcreteCreator2.new
    5.times do
        s = get
        put c1.anOperation(s), c2.anOperation(s)
    end
end

start_solve

Заготовка для языка Java очень похожа на заготовку для языка C#. Основным ее отличием является то, что в данном случае дополнительные классы описываются не внутри основного класса MyTask, содержащего метод solve, а перед ним. Кроме того, в конце класса MyTask содержится описание стартового метода main (это описание не требуется изменять, поэтому здесь оно не приводится). В начале заготовки содержится директива import java.util.ArrayList, поскольку структура данных ArrayList может оказаться полезной при решении многих задач.

[Java]

import java.util.ArrayList;

abstract class Product {
    public abstract String getInfo();
    public abstract void transform();
}

// Implement the ConcreteProduct1
//   and ConcreteProduct2 descendant classes

abstract class Creator {
    protected abstract Product factoryMethod(String info);

    public String anOperation(String info) {
        Product p = factoryMethod(info);
        p.transform();
        p.transform();
        String s = p.getInfo();
        return s;
    }
}

// Implement the ConcreteCreator1
// and ConcreteCreator2 descendant classes;
// the anOperation method should not be
// overridden in these classes

public class MyTask extends PT
{
    public static void solve() throws Exception
    {
        task("OOP1Creat1");

    }

    <Здесь содержится описание метода main>
}

Приведем реализацию классов ConcreteProduct1, ConcreteProduct2, ConcreteCreator1, ConcreteCreator2 и метода solve. Эти реализации также очень похожи на соответствующие реализации для языка C# (обратите внимание на модификаторы @Override):

[Java]

class ConcreteProduct1 extends Product {
    private String info;

    public ConcreteProduct1(String info) {
        this.info = info.toLowerCase();
    }

    @Override
    public String getInfo() {
        return info;
    }

    @Override
    public void transform() {
        StringBuffer s = new StringBuffer(info);
        for (int i = s.length() - 2; i >= 0; i--)
            if (s.charAt(i) != ' ')
                s.insert(i+1, ' ');
        this.info = s.toString();
    }
}

class ConcreteProduct2 extends Product {
    private String info;

    public ConcreteProduct2(String info) {
        this.info = info.toUpperCase();
    }

    @Override
    public String getInfo() {
        return info;
    }

    @Override
    public void transform() {
        StringBuffer s = new StringBuffer(info);
        for (int i = s.length() - 2; i >= 0; i--)
            if (s.charAt(i) != '*')
                s.insert(i+1, "**");
        this.info = s.toString();
    }
}

class ConcreteCreator1 extends Creator {
    @Override
    protected Product factoryMethod(String info) {
        return new ConcreteProduct1(info);
    }
}

class ConcreteCreator2 extends Creator {
    @Override
    protected Product factoryMethod(String info) {
        return new ConcreteProduct2(info);
    }
}

    public static void solve() throws Exception
    {
        task("OOP1Creat1");
        ConcreteCreator1 c1 = new ConcreteCreator1();
        ConcreteCreator2 c2 = new ConcreteCreator2();
        for (int i = 0; i < 5; i++) {
            String info = getString();
            put(c1.anOperation(info), c2.anOperation(info));
        }
    }

Подобно тому как в языке C# мы использовали вспомогательный класс StringBuilder для эффективного преобразования поля info в методе Transform, в языке Java мы для этих же целей используем класс StringBuffer, обладающий аналогичными свойствами.

Наконец, обратимся к языку Julia. Для решения задач на этом языке с применением электронного задачника Programming Taskbook можно использовать среду Visual Studio Code, если установить для нее расширение для данного языка (и установить на компьютер саму систему Julia).

Заготовка задания OOP1Creat1 для языка Julia имеет следующий вид:

[Julia]

include("PT.jl")

abstract type Product
end

abstract type Creator
end

mutable struct ConcreteProduct1 <: Product
    info::String
    ConcreteProduct1(info::String) = new(lowercase(info))
end

function getInfo(p::ConcreteProduct1)
    p.info
end

function transform!(p::ConcreteProduct1)
    # Implement the function
end

struct ConcreteCreator1 <: Creator
end

function factoryMethod(::ConcreteCreator1, info::String)
    ConcreteProduct1(info)
end

# Implement the ConcreteProduct2,
# ConcreteCreator2 descendant structs
# and all required functions;
# the anOperation function should not be
# overloaded for the ConcreteCreator1
# and ConcreteCreator2 structs

function anOperation(c::Creator, info::String)
    p = factoryMethod(c, info)
    transform!(p)
    transform!(p)
    getInfo(p)
end

function solve()
    task("OOP1Creat1")

end

start_solve()

Поскольку язык Julia достаточно сильно отличается от «традиционных» объектно-ориентированных языков, для него генерируются более развернутые заготовки, наглядно демонстрирующие основные особенности данного языка.

Начнем с подробного обсуждения данной заготовки. В ее начале выполняется подключение файла PT.jl, который содержит реализацию дополнительных функций задачника для языка Julia, предназначенных для ввода исходных данных, вывода результатов и отладочного вывода промежуточных данных. Затем описываются абстрактные типы Product и Creator. Абстрактные типы не могут содержать поля (что создает дополнительные проблемы при реализации некоторых паттернов), однако в нашем случае это не является существенным (поскольку в данном задании не требуется включать поля в абстрактные типы).

Далее приводится описание конкретного продукта ConcreteProduct1. Этот продукт описывается как структура struct, поскольку такая сущность, как класс (class), в языке Julia отсутствует. Выбор названия «структура» объясняется тем, что структуры в Julia могут содержать только поля, но не методы (единственным исключением являются конструкторы, которые тоже можно определить непосредственно внутри структуры, как это видно из примера описания продукта ConcreteProduct1). Вместо методов требуется описывать обычные функции, передавая им в качестве параметра тот объект, для работы с которым предназначена данная функция. Еще одной особенностью объектной модели Julia является полное отсутствие средств, связанных с инкапсуляцией данных: все поля любой структуры всегда доступны из внешней программы. Кроме того, в этом модели существенно ограничены возможности наследования, поскольку предком может быть только абстрактный тип, а от конкретного типа нельзя порождать потомков. Для обозначения наследования используется комбинация символов <:.

Структуры в Julia по умолчанию являются неизменяемыми; это означает, что после их создания нельзя изменить значения полей. Однако это ограничение можно отменить, описав структуру с ключевым словом mutable. В нашем случае продукт ConcreteProduct1 описан как изменяемая структура, поскольку при работе с ним может изменяться поле Info.

Для структуры ConcreteProduct1 уже описаны связанные с ней функции getInfo и transform!. Следует обратить внимание на стандартный способ именования функций в Julia (имя начинается с маленькой буквы, а дополнительные слова, входящие в имя, начинаются с заглавной буквы), а также на то, что имена функций, изменяющих свои параметры, рекомендуется завершать восклицательным знаком. В языке Julia, как и в языке Ruby, при достижении конца функции возвращается последнее вычисленное в ней значение.

Для создания объекта следует (как и в языках Python и Ruby) вызвать его конструктор, указав имя структуры и список параметров. Заметим, что для любой структуры сразу доступен конструктор со списком параметров, совпадающим с набором полей структуры (который создает объект и копирует параметры в соответствующие поля). Однако в структуре ConcreteProduct1 такой конструктор переопределен, чтобы в поле info записывалась строка, преобразованная функцией lowercase в нижнему регистру букв (и для этого используется особый внутренний конструктор new, который можно вызывать только внутри определения структуры).

Для завершения реализации структуры ConcreteProduct1 нам достаточно реализовать функцию transform!. Это можно сделать следующим образом, используя вспомогательную функцию transf:

[Julia]

function transf(s::String, subs::String, c::Char)
    res = ""
    for e in s
        res *= e
        if e != c
            res *= subs
        end
    end
    res[1:length(res) - length(subs)]
end

function transform!(p::ConcreteProduct1)
    p.info = transf(p.info, " ", ' ')
end

В данном варианте алгоритма (который можно реализовать и для ранее рассмотренных языков программирования) мы просто формируем требуемую строку, включая в нее символы e исходной строки и дополнительные подстроки subs, после чего удаляем «лишнюю» подстроку, добавленную после последнего символа исходной строки. Данный пример показывает, что для сцепления строк в языке Julia используется операция * (а не +, как в большинстве других языков), а также что при работе со строками можно использовать срезы. Однако синтаксис срезов в Julia имеет важные отличия от синтаксиса языка Python: индексирование выполняется от 1 (а не от 0), индекс, указанный последним, включается в срез, а если требуется указать шаг, отличный от 1, то он указывается между первым и последним индексом: [first:step:last]. Заметим, что от 1 индексируются все коллекции Julia, что необходимо принимать во внимание при решении задач, в которых в качестве исходных данных даются некоторые индексы, поскольку при генерации исходных данных предполагается, что индексы начинаются от 0.

Имея в качестве образца структуры ConcreteProduct1 и ConcreteCreator1, а также связанные с ними функции, включая вспомогательную функцию transf, мы можем легко реализовать аналогичным образом структуры ConcreteProduct2 и ConcreteCreator2:

[Julia]

mutable struct ConcreteProduct2 <: Product
    info::String
    ConcreteProduct2(info::String) = new(uppercase(info))
end

function getInfo(p::ConcreteProduct2)
    p.info
end

function transform!(p::ConcreteProduct2)
    p.info = transf(p.info, "**", '*')
end

struct ConcreteCreator2 <: Creator
end

function factoryMethod(::ConcreteCreator2, info::String)
    ConcreteProduct2(info)
end

Осталось протестировать созданные структуры и функции, выполнив требуемые действия по вводу исходных данных, их обработке и выводу результатов в функции solve. Заметим, что для ввода и вывода данных в программах на языке Julia в задачнике предусмотрены функции с теми же именами (и тем же поведением), что и для языка Python:

[Julia]

function solve()
    task("OOP1Creat1")
    c1 = ConcreteCreator1()
    c2 = ConcreteCreator2()
    for i in 1:5
        info = get()
        put(anOperation(c1, info))
        put(anOperation(c2, info))
    end
end

Запустив данную программу, мы получим сообщение о том, что задание выполнено.

Следует подчеркнуть, что правильная работы программы обеспечивается благодаря тому, что при вызове функции anOperation анализируется фактический тип структуры c1, что позволяет вызывать для нее правильный вариант фабричного метода и создавать требуемый продукт. Аналогичный анализ выполняется и при выборе варианта функции transform!, вызываемой внутри функции anOperation. Интересно отметить, что с помощью аналогичного механизма динамического определения типа параметров функций мы можем упростить наше решение, заменив реализации функции getInfo для конкретных продуктов ConcreteProduct1 и ConcreteProduct2 единственной реализацией для их общего абстрактного предка Product:

[Julia]

function getInfo(p::Product)
    p.info
end

На первый взгляд подобная реализация является неверной, поскольку абстрактный тип Product не имеет поля info. Однако такое описание параметра просто означает, что эта функция может вызываться для всех конкретных структур, порожденных от типа Product, и поскольку обе эти конкретные структуры имеют поле info, функция getInfo будет работать для них правильно. Данный пример демонстрирует роль абстрактных классов (и концепции наследования от них) в языке Julia. Важность этой роли будет проявляться при реализации практически всех паттернов проектирования на этом языке.


Prev

 

Рейтинг@Mail.ru

Разработка сайта:
М. Э. Абрамян, В. Н. Брагилевский

Последнее обновление:
01.01.2025