Интерфейс условного Builder Цепочный свободный интерфейс
Мне было интересно, что было бы лучшим способом реализовать условие .When
в свободном интерфейсе, используя цепочку методов в объекте Builder
?
Например, как бы реализовать методы .WithSkill()
и .When()
в следующем примере:
var level = 5;
var ninja = NinjaBuilder
.CreateNinja()
.Named("Ninja Boy")
.AtLevel(level)
.WithShurikens(10)
.WithSkill(Skill.HideInShadows)
.When(level > 3)
.Build()
Обновить. Примерное решение можно найти здесь.
Ответы
Ответ 1
То, что я сделал бы, - это NinjaBuilder
сохранить операции как список делегатов, а не применять их, и применять их только при вызове .Build
. Это позволит вам сделать их условными:
public class NinjaBuilder {
List<Action<Ninja>> builderActions = new List<Action<Ninja>>();
public Ninja Build() {
var ninja = new Ninja();
builderActions.ForEach(ba => ba(ninja));
return ninja;
}
public NinjaBuilder WithShurikens(int numShirukens) {
builderActions.Add(n=>n.Shirukens = numShirukens);
return this;
}
public NinjaBuilder When(Boolean condition) {
if (!condition) // If the condition is not met, remove the last action
builderActions.Remove(builderActions.Length - 1);
return this;
}
}
Конечно, это предполагает, что условие является постоянным во время создания строителя. Если вы хотите сделать это непостоянным, вы можете сделать что-то вроде этого:
public NinjaBuilder When(Func<Boolean> condition) {
var oldAction = builderActions[builderActions.Length - 1];
builderActions[builderActions.Length - 1] = n => condition() ? oldAction(n) : n;
return this;
}
Если вы хотите, чтобы When
был несколько более компилятором, вы можете сделать builderActions защищенным и сделать что-то вроде этого:
public class ConditionalNinjaBuilder : NinjaBuilder {
public ConditionalNinjaBuilder(NinjaBuilder wrappedBuilder) {
// Since someone might call .WithShirukens on the wrapping
// builder directly, we should make sure that our actions
// list is the same instance as the one in our wrapped builder
builderActions = wrappedBuilder.builderActions;
}
public ConditionalNinjaBuilder When(Func<Boolean> condition) {
var oldAction = builderActions[builderActions.Length - 1];
builderActions[builderActions.Length - 1] = n => condition() ? oldAction(n) : n;
return this;
}
}
и исходные операции возвращают ConditionalNinjaBuilder:
public ConditionalNinjaBuilder WithShurikens(int numShirukens) {
builderActions.Add(n=>n.Shirukens = numShirukens);
return new ConditionalNinjaBuilder(this);
}
Таким образом вы можете вызвать только .When
после первого вызова другого метода. У этого есть дополнительное преимущество/усложнение потенциального разрешения для вложенных/усугубленных условностей. Хлоп.
Ответ 2
У меня есть решение для цепочки интерфейсов; единственная проблема с моим решением заключается в том, что он растет по сложности (шкале) с каждым новым методом, который вы хотите поддерживать. Но это делает действительно удивительный API для пользователя.
Давайте рассмотрим, что у вас есть 3 метода, A, B и C, и вы хотите использовать их в цепочке.
Давайте также рассмотрим, что вы не хотите иметь возможность вызывать какой-либо метод более одного раза.
например.
new Builder().A().B().C(); // OK
new Builder().A().B().A(); // Not OK
Это может быть достигнуто с некоторой серьезной удивительностью:
public class Builder : A<Not_A>, B<Not_B>, C<Not_C>, Not_A, Not_B, Not_C, Not_AB, Not_BC, Not_AC, Empty
{
Not_AB A<Not_AB>.A() { return (Not_AB)A(); }
Not_AC A<Not_AC>.A() { return (Not_AC)A(); }
Empty A<Empty>.A() { return (Empty)A(); }
public Not_A A()
{
return (Not_A)this;
}
Not_AB B<Not_AB>.B() { return (Not_AB)B(); }
Not_BC B<Not_BC>.B() { return (Not_BC)B(); }
Empty B<Empty>.B() { return (Empty)B(); }
public Not_B B()
{
return (Not_B)this;
}
Not_AC C<Not_AC>.C() { return (Not_AC)C(); }
Not_BC C<Not_BC>.C() { return (Not_BC)C(); }
Empty C<Empty>.C() { return (Empty)C(); }
public Not_C C()
{
return (Not_C)this;
}
}
public interface Empty { }
public interface A<TRemainder> { TRemainder A(); }
public interface B<TRemainder> { TRemainder B(); }
public interface C<TRemainder> { TRemainder C(); }
public interface Not_A : B<Not_AB>, C<Not_AC> { }
public interface Not_B : A<Not_AB>, C<Not_BC> { }
public interface Not_C : A<Not_AC>, B<Not_BC> { }
public interface Not_AB : C<Empty> { }
public interface Not_BC : A<Empty> { }
public interface Not_AC : B<Empty> { }
И затем, смешайте это с удивительностью Криса Шаина, чтобы использовать стек действий!
Я решил реализовать его. Обратите внимание, что вы не можете вызывать какой-либо метод дважды с помощью этого цепочки решения. Я поместил ваш метод When
в качестве метода расширения.
Вот код вызова:
int level = 5;
var ninja = NinjaBuilder
.CreateNinja()
.Named("Ninja Boy")
.AtLevel(level)
.WithShurikens(10)
.WithSkill(Skill.HideInShadows)
.When(n => n.Level > 3)
.Build();
Вот мои классы ниндзя и умения:
public class Ninja
{
public string Name { get; set; }
public int Level { get; set; }
public int Shurikens { get; set; }
public Skill Skill { get; set; }
}
public enum Skill
{
None = 1,
HideInShadows
}
Это класс NinjaBuilder:
public class NinjaBuilder : NinjaBuilder_Sans_Named
{
public static NinjaBuilder CreateNinja() { return new NinjaBuilder(); }
public Stack<Action<Ninja>> _buildActions;
public NinjaBuilder()
{
_buildActions = new Stack<Action<Ninja>>();
}
public override Ninja Build()
{
var ninja = new Ninja();
while (_buildActions.Count > 0)
{
_buildActions.Pop()(ninja);
}
return ninja;
}
public override void AddCondition(Func<Ninja, bool> condition)
{
if (_buildActions.Count == 0)
return;
var top = _buildActions.Pop();
_buildActions.Push(n => { if (condition(n)) { top(n); } });
}
public override Sans_Named_NinjaBuilder Named(string name)
{
_buildActions.Push(n => n.Name = name);
return this;
}
public override Sans_AtLevel_NinjaBuilder AtLevel(int level)
{
_buildActions.Push(n => n.Level = level);
return this;
}
public override Sans_WithShurikens_NinjaBuilder WithShurikens(int shurikenCount)
{
_buildActions.Push(n => n.Shurikens = shurikenCount);
return this;
}
public override Sans_WithSkill_NinjaBuilder WithSkill(Skill skillType)
{
_buildActions.Push(n => n.Skill = skillType);
return this;
}
}
И остальная часть этого кода - просто накладные расходы, чтобы сделать преобразования и вызовы:
public abstract class NinjaBuilderBase :
EmptyNinjaBuilder,
Named_NinjaBuilder<Sans_Named_NinjaBuilder>,
AtLevel_NinjaBuilder<Sans_AtLevel_NinjaBuilder>,
WithShurikens_NinjaBuilder<Sans_WithShurikens_NinjaBuilder>,
WithSkill_NinjaBuilder<Sans_WithSkill_NinjaBuilder>
{
public abstract void AddCondition(Func<Ninja, bool> condition);
public abstract Ninja Build();
public abstract Sans_WithSkill_NinjaBuilder WithSkill(Skill skillType);
public abstract Sans_WithShurikens_NinjaBuilder WithShurikens(int shurikenCount);
public abstract Sans_AtLevel_NinjaBuilder AtLevel(int level);
public abstract Sans_Named_NinjaBuilder Named(string name);
}
public abstract class NinjaBuilder_Sans_WithSkill : NinjaBuilderBase,
Sans_WithSkill_NinjaBuilder
{
Sans_Named_WithSkill_NinjaBuilder Named_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>.Named(string name) { return (Sans_Named_WithSkill_NinjaBuilder)Named(name); }
Sans_AtLevel_WithSkill_NinjaBuilder AtLevel_NinjaBuilder<Sans_AtLevel_WithSkill_NinjaBuilder>.AtLevel(int level) { return (Sans_AtLevel_WithSkill_NinjaBuilder)AtLevel(level); }
Sans_WithShurikens_WithSkill_NinjaBuilder WithShurikens_NinjaBuilder<Sans_WithShurikens_WithSkill_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_WithShurikens_WithSkill_NinjaBuilder)WithShurikens(shurikenCount); }
}
public abstract class NinjaBuilder_Sans_WithShurikens : NinjaBuilder_Sans_WithSkill,
Sans_WithShurikens_NinjaBuilder,
Sans_WithShurikens_WithSkill_NinjaBuilder
{
Sans_Named_WithShurikens_WithSkill_NinjaBuilder Named_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>.Named(string name) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)Named(name); }
Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder AtLevel_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>.AtLevel(int level) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)AtLevel(level); }
Sans_Named_WithSkill_NinjaBuilder Named_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>.Named(string name) { return (Sans_Named_WithSkill_NinjaBuilder)Named(name); }
Sans_AtLevel_WithShurikens_NinjaBuilder AtLevel_NinjaBuilder<Sans_AtLevel_WithShurikens_NinjaBuilder>.AtLevel(int level) { return (Sans_AtLevel_WithShurikens_NinjaBuilder)AtLevel(level); }
Sans_WithShurikens_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_WithShurikens_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_WithShurikens_WithSkill_NinjaBuilder)WithSkill(skillType); }
}
public abstract class NinjaBuilder_Sans_AtLevel : NinjaBuilder_Sans_WithShurikens,
Sans_AtLevel_NinjaBuilder,
Sans_AtLevel_WithShurikens_NinjaBuilder,
Sans_AtLevel_WithSkill_NinjaBuilder,
Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder
{
EmptyNinjaBuilder Named_NinjaBuilder<EmptyNinjaBuilder>.Named(string name) { return Named(name); }
Sans_Named_AtLevel_WithSkill_NinjaBuilder Named_NinjaBuilder<Sans_Named_AtLevel_WithSkill_NinjaBuilder>.Named(string name) { return (Sans_Named_AtLevel_WithSkill_NinjaBuilder)Named(name); }
Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder WithShurikens_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)WithShurikens(shurikenCount); }
Sans_Named_AtLevel_WithShurikens_NinjaBuilder Named_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>.Named(string name) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)Named(name); }
Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)WithSkill(skillType); }
Sans_Named_AtLevel_NinjaBuilder Named_NinjaBuilder<Sans_Named_AtLevel_NinjaBuilder>.Named(string name) { return (Sans_Named_AtLevel_NinjaBuilder)Named(name); }
Sans_AtLevel_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder<Sans_AtLevel_WithShurikens_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_AtLevel_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
Sans_AtLevel_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_AtLevel_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_AtLevel_WithSkill_NinjaBuilder)WithSkill(skillType); }
}
public abstract class NinjaBuilder_Sans_Named : NinjaBuilder_Sans_AtLevel,
Sans_Named_NinjaBuilder,
Sans_Named_AtLevel_NinjaBuilder,
Sans_Named_WithShurikens_NinjaBuilder,
Sans_Named_WithSkill_NinjaBuilder,
Sans_Named_WithShurikens_WithSkill_NinjaBuilder,
Sans_Named_AtLevel_WithSkill_NinjaBuilder,
Sans_Named_AtLevel_WithShurikens_NinjaBuilder
{
EmptyNinjaBuilder WithSkill_NinjaBuilder<EmptyNinjaBuilder>.WithSkill(Skill skillType) { return (EmptyNinjaBuilder)WithSkill(skillType); }
EmptyNinjaBuilder WithShurikens_NinjaBuilder<EmptyNinjaBuilder>.WithShurikens(int shurikenCount) { return (EmptyNinjaBuilder)WithShurikens(shurikenCount); }
EmptyNinjaBuilder AtLevel_NinjaBuilder<EmptyNinjaBuilder>.AtLevel(int level) { return (EmptyNinjaBuilder)AtLevel(level); }
Sans_Named_AtLevel_WithShurikens_NinjaBuilder AtLevel_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>.AtLevel(int level) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)AtLevel(level); }
Sans_Named_WithShurikens_WithSkill_NinjaBuilder WithShurikens_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)WithShurikens(shurikenCount); }
Sans_Named_WithShurikens_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)WithSkill(skillType); }
Sans_Named_AtLevel_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
Sans_Named_AtLevel_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_Named_AtLevel_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_Named_AtLevel_WithSkill_NinjaBuilder)WithSkill(skillType); }
Sans_Named_AtLevel_NinjaBuilder AtLevel_NinjaBuilder<Sans_Named_AtLevel_NinjaBuilder>.AtLevel(int level) { return (Sans_Named_AtLevel_NinjaBuilder)AtLevel(level); }
Sans_Named_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder<Sans_Named_WithShurikens_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_Named_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
Sans_Named_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_Named_WithSkill_NinjaBuilder)WithSkill(skillType); }
}
public static class NinjaBuilderExtension
{
public static TBuilderLevel When<TBuilderLevel>(this TBuilderLevel ths, Func<Ninja, bool> condition) where TBuilderLevel : EmptyNinjaBuilder
{
ths.AddCondition(condition);
return ths;
}
}
public interface EmptyNinjaBuilder { void AddCondition(Func<Ninja, bool> condition); Ninja Build(); }
public interface Named_NinjaBuilder<TRemainder> { TRemainder Named(string name); }
public interface AtLevel_NinjaBuilder<TRemainder> { TRemainder AtLevel(int level);}
public interface WithShurikens_NinjaBuilder<TRemainder> { TRemainder WithShurikens(int shurikenCount); }
public interface WithSkill_NinjaBuilder<TRemainder> { TRemainder WithSkill(Skill skillType); }
// level one reductions
public interface Sans_Named_NinjaBuilder :
AtLevel_NinjaBuilder<Sans_Named_AtLevel_NinjaBuilder>,
WithShurikens_NinjaBuilder<Sans_Named_WithShurikens_NinjaBuilder>,
WithSkill_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
public interface Sans_AtLevel_NinjaBuilder :
Named_NinjaBuilder<Sans_Named_AtLevel_NinjaBuilder>,
WithShurikens_NinjaBuilder<Sans_AtLevel_WithShurikens_NinjaBuilder>,
WithSkill_NinjaBuilder<Sans_AtLevel_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
public interface Sans_WithShurikens_NinjaBuilder :
Named_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>,
AtLevel_NinjaBuilder<Sans_AtLevel_WithShurikens_NinjaBuilder>,
WithSkill_NinjaBuilder<Sans_WithShurikens_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
public interface Sans_WithSkill_NinjaBuilder :
Named_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>,
AtLevel_NinjaBuilder<Sans_AtLevel_WithSkill_NinjaBuilder>,
WithShurikens_NinjaBuilder<Sans_WithShurikens_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
// level two reductions
// Named
public interface Sans_Named_AtLevel_NinjaBuilder :
WithShurikens_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>,
WithSkill_NinjaBuilder<Sans_Named_AtLevel_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
public interface Sans_Named_WithShurikens_NinjaBuilder :
AtLevel_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>,
WithSkill_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
public interface Sans_Named_WithSkill_NinjaBuilder :
AtLevel_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>,
WithShurikens_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
// AtLevel
public interface Sans_AtLevel_WithShurikens_NinjaBuilder :
Named_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>,
WithSkill_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
public interface Sans_AtLevel_WithSkill_NinjaBuilder :
Named_NinjaBuilder<Sans_Named_AtLevel_WithSkill_NinjaBuilder>,
WithShurikens_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
// WithShurikens
public interface Sans_WithShurikens_WithSkill_NinjaBuilder :
Named_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>,
AtLevel_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>,
EmptyNinjaBuilder { }
// level three reductions
// Named
public interface Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder :
Named_NinjaBuilder<EmptyNinjaBuilder>,
EmptyNinjaBuilder { }
// AtLevel
public interface Sans_Named_WithShurikens_WithSkill_NinjaBuilder :
AtLevel_NinjaBuilder<EmptyNinjaBuilder>,
EmptyNinjaBuilder { }
// WithShurikens
public interface Sans_Named_AtLevel_WithSkill_NinjaBuilder :
WithShurikens_NinjaBuilder<EmptyNinjaBuilder>,
EmptyNinjaBuilder { }
// WithSkill
public interface Sans_Named_AtLevel_WithShurikens_NinjaBuilder :
WithSkill_NinjaBuilder<EmptyNinjaBuilder>,
EmptyNinjaBuilder { }
Ответ 3
Можно подумать о написании перегруженных версий С, а во втором - о том, где в качестве аргумента:
var level = 5;
var ninja = NinjaBuilder
.CreateNinja()
.Named("Ninja Boy")
.AtLevel(level)
.WithShurikens(10)
.WithSkill(Skill.HideInShadows, Where.Level(l => l > 3))
.Build()
Конечно, это связано с тем, что вы собираетесь писать "Где" как отдельный объект целиком, это выглядит примерно так:
public sealed static class Where
{
public bool Defense (Func<int, bool> predicate) { return predicate(); }
public bool Dodge (Func<int, bool> predicate) { return predicate(); }
public bool Level (Func<int, bool> predicate) { return predicate(); }
}
Ответ 4
У вас может быть условный необязательный параметр в вашем методе true
по умолчанию:
.WithSkill(Skill.HideInShadows, when: level > 3)
Это, конечно, будет очень специфичным для метода WithSkill
:
public NinjaBuilder WithSkill(Skill skill, bool when = true) {
if (!when) return this;
// ...
}
Вы можете добавить его к другим методам, которые также должны быть условными.
Другой вариант - иметь метод, который устанавливает условные части построителя:
public NinjaBuilder When(bool condition, Action<NinjaBuilder> then) {
if (condition) then(this);
return this;
}
Затем вы можете написать это следующим образом:
.When(level > 3,
then: _ => _.WithSkill(Skill.HideInShadows))
Или вот так:
.When(level > 3, _=>_
.WithSkill(Skill.HideInShadows)
)
Это более общий и может использоваться с любыми методами строителя.
Вы даже можете добавить необязательный "else":
public NinjaBuilder When(bool condition, Action<NinjaBuilder> then, Action<NinjaBuilder> otherwise = null) {
if (condition) {
then(this);
}
else if (otherwise != null) {
otherwise(this);
}
return this;
}
Или, как "mixin" :
public interface MBuilder {}
public static class BuilderExtensions {
public static TBuilder When<TBuilder>(this TBuilder self, bool condition, Action<TBuilder> then, Action<TBuilder> otherwise = null)
where TBuilder : MBuilder
{
if (condition) {
then(self);
}
else if (otherwise != null) {
otherwise(self);
}
return self;
}
}
public class NinjaBuilder : MBuilder ...
Это, конечно, способ создания операторов "if" как вызовы методов. Могут также работать другие способы:
.When(level > 3) // enter "conditional" context
.WithSkill(Skill.HideInShadows)
.End() // exit "conditional" context
В этом случае строитель отслеживает, следует ли игнорировать любые вызовы методов, которые выполняются в условном контексте, если условие ложно. When
войдет в контекст, End
выйдет из него. Вы также можете вызвать вызов Otherwise()
, чтобы отметить контекст "else". Интересно, что вы могли бы также освещать другие утверждения вроде этого, например, циклы:
.Do(times: 10) // add 10 shurikens
.AddShuriken()
.End()
В этом случае вызовы, сделанные в контексте "loop", должны быть записаны и воспроизведены требуемое количество раз, когда вызывается End
.
Таким образом, контексты являются своего рода состоянием, на котором может быть строитель; они меняют свое поведение. Вы также можете вложить контексты, используя стек, чтобы отслеживать их. И вы должны проверить, являются ли определенные вызовы действительными в определенных состояниях и, возможно, выбрасывают исключения, если они не являются.