-
Notifications
You must be signed in to change notification settings - Fork 11
Open
Description
pNode Refactoring Guide
Context
This guide is used as a reference only. Other projects that use pNode or need to improve code will use this as a reference guide.
Overview
This guide provides a comprehensive refactoring approach to transform the legacy pNode class from a tightly-coupled Windows Forms implementation to a testable, SOLID-compliant architecture.
Problem Statement
The current pNode class inherits from TreeNode (Windows Forms), making it:
- Difficult to unit test
- Tightly coupled to UI framework
- Hard to maintain and extend
- Violates SOLID principles
Proposed Solution Architecture
1. Core Data Interface (INodeData.cs)
using System;
using System.Collections.Generic;
namespace pWordLib.dat
{
/// <summary>
/// Core node data interface - completely UI agnostic
/// </summary>
public interface INodeData
{
string Name { get; set; }
string Text { get; set; }
object Tag { get; set; }
INodeData Parent { get; }
IList<INodeData> Children { get; }
// Attributes
IList<string> GetAttributeKeys();
string GetAttributeValue(string key);
void AddAttribute(string key, string value);
void DeleteAttribute(string key);
// Operations
void AddOperation(IOperate operation);
void ClearOperations();
void PerformOperations();
bool HasChangedOperations();
int OperationsCount();
// Namespace
NameSpace Namespace { get; set; }
// Core functionality
INodeData Clone();
List<INodeData> Find(string searchText, int index);
}
}2. Pure Data Implementation (PNodeData.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
namespace pWordLib.dat
{
/// <summary>
/// Pure data implementation - no UI dependencies
/// </summary>
[Serializable]
public class PNodeData : INodeData
{
private readonly SortedList<string, string> _attributes = new SortedList<string, string>();
private readonly List<IOperate> _operations = new List<IOperate>();
private readonly List<INodeData> _children = new List<INodeData>();
private INodeData _parent;
public string Name { get; set; }
public string Text { get; set; }
public object Tag { get; set; }
public NameSpace Namespace { get; set; }
public string ErrorString { get; set; }
public INodeData Parent => _parent;
public IList<INodeData> Children => _children;
public PNodeData(string name = "", string text = "")
{
Name = name;
Text = text;
}
public void AddChild(INodeData child)
{
_children.Add(child);
if (child is PNodeData pChild)
{
pChild._parent = this;
}
}
public IList<string> GetAttributeKeys() => _attributes.Keys;
public string GetAttributeValue(string key)
{
return _attributes.TryGetValue(key, out var value) ? value : null;
}
public void AddAttribute(string key, string value)
{
_attributes[key] = value;
}
public void DeleteAttribute(string key)
{
_attributes.Remove(key);
}
public void AddOperation(IOperate operation)
{
ClearOperations(); // As per original
_operations.Add(operation);
operation.Operate(this as pNode); // Will need adapter
}
public void ClearOperations()
{
_operations.Clear();
}
public void PerformOperations()
{
foreach (var operation in _operations)
{
operation.Operate(this as pNode); // Will need adapter
(operation as IChange)?.ChangeFalse(this as pNode);
}
}
public bool HasChangedOperations()
{
return _operations.Any(m => m.Changed);
}
public int OperationsCount() => _operations.Count;
public INodeData Clone()
{
var clone = new PNodeData(Name, Text)
{
Tag = Tag,
Namespace = Namespace?.Clone() as NameSpace,
ErrorString = ErrorString
};
foreach (var kvp in _attributes)
{
clone._attributes.Add(kvp.Key, kvp.Value);
}
foreach (var operation in _operations)
{
clone._operations.Add(operation);
}
foreach (var child in _children)
{
clone.AddChild(child.Clone());
}
return clone;
}
public List<INodeData> Find(string searchText, int index)
{
if (string.IsNullOrWhiteSpace(searchText))
return null;
var results = new List<INodeData>();
if ((Text ?? "").Contains(searchText) ||
((string)Tag ?? "").Contains(searchText) ||
_attributes.ContainsKey(searchText) ||
_attributes.ContainsValue(searchText))
{
results.Add(this);
}
foreach (var child in _children)
{
var childResults = child.Find(searchText, 0);
if (childResults != null)
results.AddRange(childResults);
}
return results;
}
}
}3. UI Adapter Interface (ITreeNodeAdapter.cs)
using System.Windows.Forms;
namespace pWordLib.dat
{
/// <summary>
/// Adapter interface for UI binding
/// </summary>
public interface ITreeNodeAdapter
{
TreeNode TreeNode { get; }
INodeData NodeData { get; }
void SyncToTreeNode();
void SyncFromTreeNode();
}
}4. Windows Forms Adapter (PNodeTreeAdapter.cs)
using System;
using System.Windows.Forms;
namespace pWordLib.dat
{
/// <summary>
/// Adapter that bridges NodeData with TreeNode for Windows Forms
/// </summary>
public class PNodeTreeAdapter : TreeNode, ITreeNodeAdapter
{
private readonly INodeData _nodeData;
public TreeNode TreeNode => this;
public INodeData NodeData => _nodeData;
public PNodeTreeAdapter(INodeData nodeData) : base()
{
_nodeData = nodeData ?? throw new ArgumentNullException(nameof(nodeData));
SyncToTreeNode();
}
public void SyncToTreeNode()
{
Name = _nodeData.Name;
Text = _nodeData.Text;
Tag = _nodeData.Tag;
// Sync children
Nodes.Clear();
foreach (var child in _nodeData.Children)
{
Nodes.Add(new PNodeTreeAdapter(child));
}
}
public void SyncFromTreeNode()
{
_nodeData.Name = Name;
_nodeData.Text = Text;
_nodeData.Tag = Tag;
}
// Delegate all node operations to the data model
public void AddAttribute(string key, string value)
{
_nodeData.AddAttribute(key, value);
}
public void AddOperation(IOperate operation)
{
_nodeData.AddOperation(operation);
}
// ... other delegated methods
}
}Migration Strategy
Phase 1: Create New Structure
- Create the new interface and implementation files
- Ensure all existing functionality is covered
- Write comprehensive unit tests for
PNodeData
Phase 2: Adapter Implementation
- Implement the adapter pattern to bridge old and new code
- Test adapter functionality with existing UI
Phase 3: Gradual Migration
- Replace direct
pNodeusage withINodeDatainterface - Use dependency injection where appropriate
- Update existing code to use adapters
Phase 4: Legacy Code Removal
- Once all references are migrated, deprecate old
pNodeclass - Clean up any remaining dependencies
Benefits of This Approach
- Testability: Pure data classes can be unit tested without UI dependencies
- Flexibility: Can switch UI frameworks (WPF, WinUI, etc.) by implementing new adapters
- Maintainability: Clear separation of concerns makes code easier to understand and modify
- SOLID Compliance:
- Single Responsibility: Each class has one clear purpose
- Open/Closed: Extend via interfaces without modifying existing code
- Liskov Substitution: Any
INodeDataimplementation can be used interchangeably - Interface Segregation: Focused, specific interfaces
- Dependency Inversion: Depend on abstractions, not concrete implementations
Example Usage
Unit Testing (No UI Required)
[Test]
public void TestNodeDataOperations()
{
// Arrange
var nodeData = new PNodeData("test", "Test Node");
// Act
nodeData.AddAttribute("key", "value");
// Assert
Assert.AreEqual("value", nodeData.GetAttributeValue("key"));
}
Windows Forms Integration
// Create data model
var nodeData = new PNodeData("ui", "UI Node");
nodeData.AddAttribute("type", "example");
// Create UI adapter
var treeNode = new PNodeTreeAdapter(nodeData);
// Add to TreeView
treeView.Nodes.Add(treeNode);Additional Considerations
- Serialization: Update serialization logic to work with
INodeData - XML Export: Move XML generation logic to a separate service class
- Operations: Consider creating an
IOperationServiceto handle operations - Performance: Profile and optimize the adapter layer if needed
Next Steps
- Review and approve the proposed architecture
- Set up a feature branch for the refactoring
- Create unit test project for the new implementation
- Begin incremental migration following the phases above
- Document any API changes for consumers of the library
File Structure
pWordLib/
├── dat/
│ ├── Interfaces/
│ │ ├── INodeData.cs
│ │ └── ITreeNodeAdapter.cs
│ ├── Implementation/
│ │ └── PNodeData.cs
│ ├── Adapters/
│ │ └── PNodeTreeAdapter.cs
│ └── Legacy/
│ └── pNode.cs (deprecated)
└── Tests/
└── PNodeDataTests.csThis refactoring will transform your 2006 codebase into a modern, testable, and maintainable solution while preserving all existing functionality.
Ensure successful build and should not have to utilize Windows Forms libraries within pWordLib or Windows Forms within a testing framework with regrard to pNode.
Metadata
Metadata
Assignees
Labels
No labels