Что следует учитывать, чтобы сделать изменяемый класс неизменным? Например: Можем ли мы иметь методы push и pop в классе неизменяемых стеков? Или мы просто должны удалить любой метод, который изменяет состояние объекта-объекта?Mutable vs. Immutable
ответ
Суть: Вы бы лучше, чтобы удалить метод, изменяющее состояние экземпляра объекта из класса. Но если вы хотите сохранить его, тогда он должен создать новый объект с другим состоянием из исходного и вернуть новый объект обратно.
Вот более конкретный ответ:
Там не должно быть никаких методов изменения состояния объекта в классе неизменяемых.
Существует много методов пустот, которые изменяют состояние этого объекта в изменяемом классе. Поэтому мы должны изменить их подписи таким образом, чтобы вернуть новый объект вместо изменения состояния этого «объекта».
Также существует множество непустых методов, которые изменяют состояние этого «объекта» и возвращают значение, которое они изменили в «этом». Подпись этих методов также должна быть изменена таким образом, что они возвращают новый объект вместо изменения состояния «this». Говоря о списках, обычно требуется другой метод (например, «peek»), чтобы получить определенное значение. Проверьте образец ниже, чтобы получить то, что я имею в виду:
Проверьте этот «толчок» и «поп» методы изменяемого класса стека:
public class Stack <E> {
…
public void push (E e) {
ensureCapacity(); // This method checks for capacity
elements[size++] = e;
}
Этот метод добавляет новый элемент в верхней части стек и изменяет состояние этого «объекта» таким образом.
public E pop() {
if (size == 0) throw new IllegalStateException("Stack.pop");
E result = elements[--size];
elements[size] = null;
return result;
}
…
}
Этот метод удаляет элемент в верхней части стека и возвращает его обратно, и изменяет состояние «этого» объекта путем удаления элемента.
Теперь предположим, что нам нужно изменить эти методы, чтобы сделать этот стек неизменным.Давайте сначала рассмотрим метод «push»:
«push» - это метод пустоты, который изменяет состояние «этого» объекта, добавляя к нему новый элемент. Для того, чтобы стек неизменны мы создадим новый стек, похожий на «это» и добавить новый элемент в новом стеке и вернуть его обратно:
public class ImmStack <E> {
...
/**
* this method pushes the item to a new object and keeps the original object unchanged
* @param e The item to be pushed
* @return A new list
* @PRE An original list object is required as well as an item to be pushed
* @POST A new list would be returned
*/
@SuppressWarnings({ "unchecked", "rawtypes" }) // All items in this.elements[] are of type E
public ImmStack<E> push (E e) {
ImmStack<E> stc = new ImmStack(getNewElements());
stc.elements=ensureCapacity(stc.elements);
stc.elements[size] = e;
stc.size = size +1;
return stc;
}
метод «поп» изменяет состояние «этого» объекта, удаление элемента. Для того, чтобы класс неизменяемыми мы reate новый стек, похожий на «это» и удалить элемент из этого нового стека и вернуть его обратно:
/**
* This pop method returns a new stack without the element at the top of the original list
* @return The new stack
* @POST The new stack would be returned
*/
@SuppressWarnings({ "unchecked", "rawtypes" }) // All items in this.elements[] are of type E
public ImmStack<E> pop() {
if (size == 0) throw new IllegalStateException("Stack.pop");
ImmStack<E> stc = new ImmStack(getNewElements());
stc.elements=ensureCapacity(stc.elements);
stc.elements[size-1] = null;
stc.size=size-1;
return stc;
}
старый «поп» метод возвращал элемент в верхней части. Мы также нуждаемся в новом методе, который возвращает элемент вверху, чтобы покрыть эту функцию:
/**
* Returns item at front of queue without removing.
* @return item at front
* @throws java.util.NoSuchElementException if empty
*/
public E top()
{
if (this.isEmpty())
{
throw new NoSuchElementException("Queue underflow");
}
return elements[size-1];
}
Это был просто пример. У вас может быть больше методов для изменения в вашем классе, чтобы сделать его неизменным.
Если ваш стек неизменен, то по определению его нельзя изменить. Методы push()
и pop()
не могут быть выполнены.
Если метод не может быть успешно завершен, вы можете создать исключение. Когда метод может никогда не будет выполнен успешно, стандартное исключение для броска - UnsupportedOperationException
.
Например:
public E[] push (E e) {
throw new UnsupportedOperationException();
}
EDIT:
Вы отмечаете в комментариях, что ваш метод толчка() только возвращающиеся глубокую копию стеки с новым элементом. Похоже, вы представляете неизменяемый стек как экземпляр класса, а толкаемый стек - как массив.
Вы можете получить размер одного из двух массивов, на который ссылается newElements
, с newElements.length
. Таким образом, вы могли бы написать такой код:
public E[] push (E e) {
E[] newElements=getNewElements();
int oldLength = newElements.length;
newElements=ensureCapacity(newElements);
int lastIndexInNewArray = oldLength;
newElements[ lastIndexInNewArray ] = e;
return newElements;
}
Ниже приведена реализация неизменяемого стека в C#.
Нажатие и выталкивание возвращает вам совершенно новый стек, и Peek позволяет вам взглянуть на верхнюю часть стека, не выталкивая его.
Обратите внимание, что копирование всего стека не требуется.
Вот как неизменяемые структуры реализуются в любых нетривиальных случаях. В некоторых случаях очень важны нетривиальные неизменные структуры. Плакаты, говорящие, что это невозможно сделать, сильно дезинформированы.
Исходный код и более подробную информацию можно найти здесь:
https://blogs.msdn.microsoft.com/ericlippert/2007/12/04/immutability-in-c-part-two-a-simple-immutable-stack/
public interface IStack<T> : IEnumerable<T>
{
IStack<T> Push(T value);
IStack<T> Pop();
T Peek();
bool IsEmpty { get; }
}
public sealed class Stack<T> : IStack<T>
{
private sealed class EmptyStack : IStack<T>
{
public bool IsEmpty { get { return true; } }
public T Peek() { throw new Exception("Empty stack"); }
public IStack<T> Push(T value) { return new Stack<T>(value, this); }
public IStack<T> Pop() { throw new Exception("Empty stack"); }
public IEnumerator<T> GetEnumerator() { yield break; }
IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}
private static readonly EmptyStack empty = new EmptyStack();
public static IStack<T> Empty { get { return empty; } }
private readonly T head;
private readonly IStack<T> tail;
private Stack(T head, IStack<T> tail)
{
this.head = head;
this.tail = tail;
}
public bool IsEmpty { get { return false; } }
public T Peek() { return head; }
public IStack<T> Pop() { return tail; }
public IStack<T> Push(T value) { return new Stack<T>(value, this); }
public IEnumerator<T> GetEnumerator()
{
for(IStack<T> stack = this; !stack.IsEmpty ; stack = stack.Pop())
yield return stack.Peek();
}
IEnumerator IEnumerable.GetEnumerator() {return this.GetEnumerator();}
}
Если вы можете нажать новый элемент в стек, это не неизменная. –
Почему размер '' '' '' растет, если вы только изменили массив 'newElement'? –
Вы хотите изменить состояние объекта _immutable_? Как и почему? – Tom