From d1a5dd279cdccf20f1069594101b122d9b3fcf77 Mon Sep 17 00:00:00 2001 From: AJ Date: Sat, 22 Nov 2025 23:02:37 -0500 Subject: [PATCH] Add validation and business logic to domain models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhance Tool, BinItem, and MultiBin classes with: - Input validation in constructors and property setters - Guard clauses for invalid states (negative values, empty names) - Business logic methods (CanFitItem, CalculateWaste, etc.) - Proper encapsulation with backing fields - Comprehensive XML documentation - Override Equals/GetHashCode for value semantics - Better ToString() implementations These changes enforce business rules at the domain level and prevent invalid states from being created. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CutList/Tool.cs | 99 +++++++++++++++++++++++++++++++++++-- SawCut/BinItem.cs | 94 +++++++++++++++++++++++++++++++++-- SawCut/MultiBin.cs | 121 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 303 insertions(+), 11 deletions(-) diff --git a/CutList/Tool.cs b/CutList/Tool.cs index fde034b..c3bef52 100644 --- a/CutList/Tool.cs +++ b/CutList/Tool.cs @@ -1,16 +1,109 @@ -namespace CutList +using System; + +namespace CutList { + /// + /// Represents a cutting tool with its kerf (blade width). + /// Enforces business rules for valid tools. + /// public class Tool { - public string Name { get; set; } + private string _name; + private double _kerf; - public double Kerf { get; set; } + public Tool(string name, double kerf, bool allowUserToChange = false) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Tool name cannot be empty", nameof(name)); + + if (kerf < 0) + throw new ArgumentException("Kerf cannot be negative", nameof(kerf)); + + _name = name; + _kerf = kerf; + AllowUserToChange = allowUserToChange; + } + + /// + /// Parameterless constructor for serialization only. + /// Use the parameterized constructor for creating valid instances. + /// + public Tool() + { + } + + public string Name + { + get => _name; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Tool name cannot be empty", nameof(value)); + _name = value; + } + } + + public double Kerf + { + get => _kerf; + set + { + if (value < 0) + throw new ArgumentException("Kerf cannot be negative", nameof(value)); + _kerf = value; + } + } public bool AllowUserToChange { get; set; } + /// + /// Calculates the total material wasted for a given number of cuts. + /// + public double CalculateTotalWaste(int numberOfCuts) + { + if (numberOfCuts < 0) + throw new ArgumentException("Number of cuts cannot be negative", nameof(numberOfCuts)); + + return Kerf * numberOfCuts; + } + + /// + /// Checks if this tool is compatible with the given material thickness. + /// Some tools may have minimum/maximum thickness requirements. + /// + public bool IsCompatibleWithThickness(double thickness) + { + // For now, all tools are compatible with any thickness + // This can be extended based on specific tool requirements + return thickness > 0; + } + public override string ToString() { return Name; } + + public override bool Equals(object obj) + { + if (obj is Tool other) + { + return Name == other.Name && + Math.Abs(Kerf - other.Kerf) < 0.0001 && + AllowUserToChange == other.AllowUserToChange; + } + return false; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + (Name?.GetHashCode() ?? 0); + hash = hash * 23 + Kerf.GetHashCode(); + hash = hash * 23 + AllowUserToChange.GetHashCode(); + return hash; + } + } } } \ No newline at end of file diff --git a/SawCut/BinItem.cs b/SawCut/BinItem.cs index 03bea6f..1ec90ed 100644 --- a/SawCut/BinItem.cs +++ b/SawCut/BinItem.cs @@ -1,9 +1,97 @@ -namespace SawCut +using System; + +namespace SawCut { + /// + /// Represents an item to be placed in a bin. + /// Enforces business rules for valid items. + /// public class BinItem { - public string Name { get; set; } + private string _name; + private double _length; - public double Length { get; set; } + public BinItem(string name, double length) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Item name cannot be empty", nameof(name)); + + if (length <= 0) + throw new ArgumentException("Item length must be greater than zero", nameof(length)); + + _name = name; + _length = length; + } + + /// + /// Parameterless constructor for serialization only. + /// Use the parameterized constructor for creating valid instances. + /// + public BinItem() + { + } + + public string Name + { + get => _name; + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Item name cannot be empty", nameof(value)); + _name = value; + } + } + + public double Length + { + get => _length; + set + { + if (value <= 0) + throw new ArgumentException("Item length must be greater than zero", nameof(value)); + _length = value; + } + } + + /// + /// Checks if this item can fit in the given available length. + /// + public bool CanFitIn(double availableLength) + { + return Length <= availableLength; + } + + /// + /// Checks if this item can fit in the given available length with spacing. + /// + public bool CanFitInWithSpacing(double availableLength, double spacing) + { + return Length + spacing <= availableLength; + } + + public override string ToString() + { + return $"{Name} ({Length}\")"; + } + + public override bool Equals(object? obj) + { + if (obj is BinItem other) + { + return Name == other.Name && Length.IsEqualTo(other.Length); + } + return false; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + (Name?.GetHashCode() ?? 0); + hash = hash * 23 + Length.GetHashCode(); + return hash; + } + } } } \ No newline at end of file diff --git a/SawCut/MultiBin.cs b/SawCut/MultiBin.cs index 7a6040d..2e60387 100644 --- a/SawCut/MultiBin.cs +++ b/SawCut/MultiBin.cs @@ -1,14 +1,125 @@ -namespace SawCut +using System; + +namespace SawCut { + /// + /// Represents a type of bin with quantity and priority. + /// Enforces business rules for valid bin configurations. + /// public class MultiBin { - public int Quantity { get; set; } = 1; + private int _quantity; + private double _length; + private int _priority; - public double Length { get; set; } + public MultiBin(double length, int quantity = 1, int priority = 25) + { + if (length <= 0) + throw new ArgumentException("Bin length must be greater than zero", nameof(length)); + + if (quantity < -1 || quantity == 0) + throw new ArgumentException("Quantity must be positive or -1 for unlimited", nameof(quantity)); + + _length = length; + _quantity = quantity; + _priority = priority; + } /// - /// Lower value priority with be used first. Default to 25 + /// Parameterless constructor for serialization only. + /// Use the parameterized constructor for creating valid instances. /// - public int Priority { get; set; } = 25; + public MultiBin() + { + _quantity = 1; + _priority = 25; + } + + /// + /// Quantity of bins available. Use -1 for unlimited bins. + /// + public int Quantity + { + get => _quantity; + set + { + if (value < -1 || value == 0) + throw new ArgumentException("Quantity must be positive or -1 for unlimited", nameof(value)); + _quantity = value; + } + } + + public double Length + { + get => _length; + set + { + if (value <= 0) + throw new ArgumentException("Bin length must be greater than zero", nameof(value)); + _length = value; + } + } + + /// + /// Lower value priority will be used first. Default is 25. + /// + public int Priority + { + get => _priority; + set => _priority = value; + } + + /// + /// Checks if this bin type has unlimited quantity. + /// + public bool IsUnlimited => Quantity == -1; + + /// + /// Checks if an item of the given length can fit in this bin. + /// + public bool CanFitItem(double itemLength) + { + return itemLength <= Length && itemLength > 0; + } + + /// + /// Calculates the waste for a single bin if the given length is used. + /// + public double CalculateWaste(double usedLength) + { + if (usedLength < 0 || usedLength > Length) + throw new ArgumentException("Used length must be between 0 and bin length", nameof(usedLength)); + + return Length - usedLength; + } + + public override string ToString() + { + var quantityStr = IsUnlimited ? "Unlimited" : Quantity.ToString(); + return $"Bin {Length}\" (Qty: {quantityStr}, Priority: {Priority})"; + } + + public override bool Equals(object? obj) + { + if (obj is MultiBin other) + { + return Quantity == other.Quantity && + Length.IsEqualTo(other.Length) && + Priority == other.Priority; + } + return false; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 23 + Quantity.GetHashCode(); + hash = hash * 23 + Length.GetHashCode(); + hash = hash * 23 + Priority.GetHashCode(); + return hash; + } + } } } \ No newline at end of file