Как сделать класс неизменным, если он ссылается на изменяемый объект? - PullRequest
4 голосов
/ 29 апреля 2019

Допустим, у меня в java есть класс Employee , который выглядит примерно так

public class Employee {
    private String empName;
    private int empId;

    public String getEmpName() {
        return empName;
    }
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    public int getEmpId() {
        return empId;
    }
    public void setEmpId(int empId) {
        this.empId = empId;
    }
}

Теперь я хочу использовать этот объект в неизменном классе, скажем, компании вследующий формат.и условие состоит в том, что я не могу изменить

public final class Company {
    final String companyName;
    final Employee employee;

    public Company(String companyName, Employee employee) { 
        this.companyName = companyName; 
        this.employee = employee; 
    } 
    public String getCompanyName() { 
        return companyName; 
    } 
    public Employee getEmployee() { 
        return employee; 
    }
}

Поэтому мой вопрос заключается в том, является ли это правильным способом сделать класс Company неизменным, когда я ссылаюсь на внутренний объект, который можно изменить?

Ответы [ 4 ]

1 голос
/ 29 апреля 2019

2 вещи, которые пришли мне в голову:

  1. Добавьте ReadOnlyEmployee Интерфейс для вашего Employee, который выставляет только геттеры. Тогда вам придется изменить тип возвращаемого значения getEmployee() на ReadOnlyEmployee. Преимущество этого решения в том, что оно понятно и явно для пользователя. Проблема в том, что метод получения возвращает другой тип, чем принимает конструктор, что может сбивать с толку.

  2. Добавьте прокси-класс, который расширяет класс Employee, который выбрасывает IllegalAccessException или аналогичный при вызовах сеттера. Преимущество заключается в том, что вам не нужно вводить новые интерфейсы или изменять методы Company. Недостатком является возможные исключения во время выполнения.

1 голос
/ 29 апреля 2019

Как указано в этой статье https://www.journaldev.com/129/how-to-create-immutable-class-in-java, выполните глубокое клонирование объекта Employee в своем конструкторе конечного класса.Таким образом, вы не будете использовать ссылку на объект.

0 голосов
/ 29 апреля 2019

Технически, нет.Добавление final делает ссылку неизменной: вы не можете назначить другой объект Employee.this.employee = ... невозможно.

Однако окончательность не является заразной, как константность в C ++.Еще можно вызвать getEmployee().setEmpName(...) или getEmployee().setEmpId(...) и изменить объект сотрудника.Вы не можете заменить его новым, но вы можете изменить находящийся там объект.

Если вы хотите сделать Company полностью неизменным, вам нужно сделать защитные копии объекта Employee в двамест.Во-первых, вам нужно скопировать объект, переданный в конструктор.Во-вторых, вам нужно вернуть копию с getEmployee(), чтобы предотвратить раскрытие внутреннего объекта.

public final class Company {
    final String companyName;
    final Employee employee;

    public Company(String companyName, Employee employee) { 
        this.companyName = companyName; 
        this.employee = new Employee(employee);     // 1
    } 
    public String getCompanyName() { 
        return companyName; 
    } 
    public Employee getEmployee() { 
        return new Employee(employee);              // 2
    }
}
0 голосов
/ 29 апреля 2019

Проблема в том, что вы освобождаете ссылку на экземпляр сотрудника, поэтому вызывающая сторона может изменить объект.

  1. Вы возвращаете ссылку на копию сотрудника и перестаете беспокоиться о том, что будет дальше. Вы защитили базовый экземпляр. Вызывающая сторона может делать с копией все, что пожелает, в то время как ваше поле остается согласованным и практически неизменным (фактически, оно может изменяться).

    public class Employee {
        public Employee(Employee o) {
            // copy evething you need from o
        }
    }
    
    public final class Company {
        public Employee getEmployee() { 
            return new Employee(employee); 
        }
    }
    

    Проблемы здесь? Вызывающий абонент изменяет данные сотрудника и не может понять, почему внутри компании ничего не изменилось.

  2. Вы возвращаете ссылку на внутренний подкласс Company Employee. В этом классе вы переопределяете сеттеры и другие методы, которые изменяют состояние. Например, вызывающий может получить UnsupportedOperationException, когда он вызывает такие модифицирующие методы для извлеченного Employee.

    public final class Company {
        private final CompanyEmployee companyEmployee;
    
        public Company(String companyName, Employee employee) { 
            this.companyName = companyName; 
            companyEmployee = new CompanyEmploye(employee); 
        }
    
        private static class CompanyEmployee extends Employee {
            public Employee(Employee o) {
                super(o);
            }
    
            public void setEmpName(String empName) {
                throw new UnsupportedOperationException();
            }
            public void setEmpId(int empId) {       
                throw new UnsupportedOperationException();
            }
        }
    
        public Employee getEmployee() { 
            return companyEmployee;
        }
    }
    

    Проблемы здесь? Наследование используется для контроля доступа.

  3. В противном случае неизменный класс, состоящий из изменяемых компонентов, не будет , что неизменным.

Является ли это правильным способом сделать класс Company неизменным, когда я ссылаюсь на внутренний объект, который можно изменить?

Нет. Насколько я понимаю, любой компонент, полученный из экземпляра неизменяемого класса, не должен быть изменяемым. Независимо от того, на каком уровне может возникнуть запрос на изменение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...