Как правильно организовать классы?

192
25 февраля 2022, 05:10

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

public abstract class Operator implements AbstractToken{
    protected String value;
    protected int priority;
    protected int countOperands;
    public String getValueToken() {
        return this.value;
    }
    protected double doСalculate(double[] operands) {
        return 0;   
    }
    public double calculate(double[] operands) {
        return doСalculate(operands);
    }
    public int countOperands() {
        return this.countOperands;
    }
    public int getPriority() {
        return this.priority;
    }
}

Классы Sub, Div, Mult, Pow реализованы аналогично Add

public class Add extends Operator {
    Add() {
        this.value = "+";
        this.priority = 0;
        this.countOperands = 2; 
    }
    @Override
    protected double doСalculate(double[] operands) {
        return operands[0] + operands[1];   
    }
}

Класс Token

public class Token implements AbstractToken {
    String value;
    Token(String value) {
        this.value = value;
    }
    public boolean isCloseBracket(){
        return this.value == ")";
    }
    public boolean isOpenBracket(){
        return this.value == "(";
    }
}

Идея заключалась в том, чтобы поместить с помощью интерфейса AbstractToken в массив объекты Operator, Token. А дальше работать с массивом, реализуя алгоритм. Концептуально я представлял себе так:

private ArrayList<AbstractToken> toPostfix(ArrayList<AbstractToken> tokens) {
        ArrayList<AbstractToken> infexExpression = new ArraList<AbstractToken>()
        Stack<AbstractToken> stack = new Stack<AbstractToken>()
        for (AbstractToken token : tokens) {
            if(token instanceof Operator){
                //...
            }
            else {
                //...
            }          
        }
}

Однако я столкнулся с тем, что не могу обращаться к методам классов Operator и Token, что вполне очевидно, когда понимаешь для чего необходимо использовать интерфейсы. Подумав и посмотрев на этот код, я понял, что это полное безобразие и не понимаю ооп. Вопросы:

  1. Как правильно организовать классы для такого калькулятора?
  2. Есть ли какие нибудь паттерны для того, чтобы организовать корректно архитектуру классов?
Answer 1

Интересный вопрос и самое смешное, что я действительно не смог найти ООП реализации (только процедурный код), посему набросал простенькое решение.

public interface Token {
    //pattern 'Factory method '
    public static Token of(String token) {        
        try {            
            switch (token) {
                case "+": return new Add();
                case "*": return new Mult();
                case "-": return new Sub();
                case "/": return new Div();
                case "^": return new Pow();                
                case "(": return new BracketOpen();
                case ")": return new BracketClose();
            }            
            return new TokenNumber(Double.valueOf(token));            
        } catch (NumberFormatException | NullPointerException e) {
            throw new UnsupportedOperationException("Not supported for token " + token);
        }        
    }
    public int getPriority();    
    public Double execute(CalculationPool calculationPool);    
    public void addToken(CalculationPool calculationPool);
}
public abstract class TokenImpl implements Token{
    private final int priority;
    public TokenImpl(int priority) {
        this.priority = priority;
    }
    @Override
    public int getPriority() {
        return priority;
    }
}
public class TokenNumber extends TokenImpl{
    private final Double value;
    public TokenNumber(Double value) {
        super(-1);
        this.value = value;
    }
    @Override
    public String toString() {
        return String.valueOf(value);
    }
    @Override
    public Double execute(CalculationPool calculationPool) {
        return value;
    }
    @Override
    public void addToken(CalculationPool calculationPool) {
        calculationPool.addToQueue(this);
    }
}
import java.util.function.BinaryOperator;
public abstract class TokenFunction extends TokenImpl {
    public TokenFunction(int priority) {
        super(priority);
    }
    @Override
    public void addToken(CalculationPool calculationPool) {
        calculationPool.toStack(this);
    }
    //pattern 'Template method '
    @Override
    public Double execute(CalculationPool calculationPool) {
        return getOperation().apply(calculationPool.pollFromNumber(), calculationPool.pollFromNumber());
    }
    protected abstract BinaryOperator<Double> getOperation();
}
public abstract class TokenBracket extends TokenImpl{
    public TokenBracket(int priority) {
        super(priority);
    }
    @Override
    public Double execute(CalculationPool calculationPool) {
        throw new UnsupportedOperationException("Unsupported operation for this token : " + this); 
    }
}
public class BracketClose extends TokenBracket{
    public BracketClose() {
        super(1);
    }
    @Override
    public void addToken(CalculationPool calculationPool) {
        calculationPool.toStack(this);
        calculationPool.pollFromStack();
        calculationPool.pollFromStack();
    }
    @Override
    public String toString() {
        return ")";
    }
}
public class BracketOpen extends TokenBracket{
    public BracketOpen() {
        super(0);
    }
    @Override
    public void addToken(CalculationPool calculationPool) {
        calculationPool.addToStack(this);
    }
    @Override
    public String toString() {
        return "(";
    }
}
import java.util.function.BinaryOperator;
public class Add extends TokenFunction {
    public Add() {
        super(2);
    }
    @Override
    protected BinaryOperator<Double> getOperation() {
        return (x,y)->y+x;
    }
    @Override
    public String toString() {
        return "+";
    }
}
import java.util.function.BinaryOperator;
public class Div extends TokenFunction{
    public Div() {
        super(3);
    }
    @Override
    protected BinaryOperator<Double> getOperation() {
        return (x,y)->y/x;
    }
    @Override
    public String toString() {
        return "/";
    }
}
import java.util.function.BinaryOperator;
public class Mult extends TokenFunction {
    public Mult() {
        super(3);
    }
    @Override
    protected BinaryOperator<Double> getOperation() {
        return (x,y)->y*x;
    }
    @Override
    public String toString() {
        return "*";
    }
}
import java.util.function.BinaryOperator;
public class Pow extends TokenFunction {
    public Pow() {
        super(4);
    }
    @Override
    protected BinaryOperator<Double> getOperation() {
        return (x,y)->Math.pow(y,x);
    }
    @Override
    public String toString() {
        return "^";
    }
}
import java.util.function.BinaryOperator;
public class Sub extends TokenFunction{
    public Sub() {
        super(2);
    }
    @Override
    protected BinaryOperator<Double> getOperation() {
        return (x,y)->y-x;
    }
    @Override
    public String toString() {
        return "-";
    }
}
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
public class CalculationPool {
    private final Deque<Token> OUTPUT_QUEUE = new ArrayDeque<>();    
    private final Deque<Token> STACK = new ArrayDeque<>();
    private final Deque<Double> NUMBERS = new ArrayDeque<>();
    public CalculationPool() {}
    public CalculationPool(String ... values) {
        addToken(values);
    }
    public Token pollFromStack (){
        return STACK.pollFirst();
    }    
    public Double pollFromNumber (){
        return NUMBERS.pollLast();
    }
    public void addToQueue (Token token){
        OUTPUT_QUEUE.add(token);        
    }
    public void addToStack (Token token){
        STACK.addFirst(token);        
    }
    public void toStack(Token token) {
        final Iterator<Token> iterator = STACK.iterator();        
        while (iterator.hasNext()) {
            final Token nextToken = iterator.next();            
            if (token.getPriority() > nextToken.getPriority()) break;
            addToQueue(nextToken);
            iterator.remove();
        }
        addToStack(token);
    } 
    public CalculationPool addToken(String value){
        Token.of(value).addToken(this);
        return this;
    }
    public final CalculationPool addToken(String ... values){
        for (String value : values) addToken(value);
        return this;
    }
    public Double calculate() {
        OUTPUT_QUEUE.addAll(STACK);
        OUTPUT_QUEUE.forEach(token->NUMBERS.add(token.execute(this)));
        return NUMBERS.getFirst();
    }
}
public class Main {
    public static void main(String[] args) {
        //можно инициализировать так
        final Double result1 = new CalculationPool()
                .addToken("7").addToken("+")
                .addToken("5").addToken("*").addToken("2")
                .calculate();
        //а можно так
        final Double result2 = new CalculationPool()
                .addToken("3", "+", "4", "*", "2", "/", "(", "1", "-", "5", ")", "^", "2")
                .calculate();
        //или так
        final Double result3 = new CalculationPool("(", "6", "+", "10", "-", "4", ")", "/", "(", "1", "+", "1", "*", "2", ")", "+", "1")
                .calculate();
        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
    }
}

Парсер математически выражений на вашей совести. Если есть вопросы - задавайте. Удачи!

READ ALSO
ScheduledExecutorService для длительного времени: schedule VS scheduleAtFixedRate

ScheduledExecutorService для длительного времени: schedule VS scheduleAtFixedRate

Как лучше организовать заведомо долгий отложенный поток?

176
Улучшения ввода/вывода для алгоритма

Улучшения ввода/вывода для алгоритма

Решил порешать задачи которые готовит ЯндексНаткнулся на первые трудности в виде задачи про дубликаты:

113
Помогите разобраться с версткой

Помогите разобраться с версткой

Использую библиотеку materializecssНа главной странице проекта вывожу раскрывающиеся карточки, в три колонки

79
Возможно ли открыть папку на хостинге

Возможно ли открыть папку на хостинге

Возможно ли открыть папку на хостинге через opendir()

92