我无法找出重构非常大的C#类的最佳方法,特别是如何将大类中的共享属性/值传递到提取的类中,并在主类中提供这些修改后的值。
一开始,这个类长1000行并且非常程序化 - 它涉及调用方法并按特定顺序执行工作。一路上,事物被持久化到数据库中。在此过程中,有多个项目列表在方法中处理和共享。在此过程结束时,会向用户显示一组统计信息。随着处理的进行,这些统计数据以各种方法计算。粗略概述 - 该过程涉及一系列随机选择,在过程结束时,用户可以看到有多少随机项,挑选了多少个无效记录,这个子列表中有多少项等。
我一直在阅读鲍勃叔叔的“清洁代码”,并试图确保我重构每个班级只做一件事。
所以我已经能够提取方法和类以保持文件更小(现在减少到450行)但我现在遇到的问题是这些分解的类需要从主父类到传递给他们并更新 - 这些值也将用于其他方法/类方法。
我很沮丧,这是最干净的方法:
1)我应该创建一堆私有成员变量来将统计值和列表存储在主类中,然后在调用主类后调用它们。 dependnat类方法,接收一个复杂的结果类,然后提取这些值并填充/更新私有成员变量? (这种方式有很多锅炉板代码)
OR
2)创建DTO或某种包含Lists和统计值的容器类是否更好,只需通过引用将其传递给各种类方法和子类方法,以构建值列表?换句话说,我只是传递这个容器类,因为它是一个对象,其他类和方法将能够直接操作那里的值。然后在过程结束时,值DTO / container /你要调用它的任何内容将包含所有最终结果,我可以从容器类中提取它们(在这种情况下,确实没有必要提取它们并填充主类的私有成员变量。)
后者是我现在拥有它的方式,但我觉得这是一种代码味道 - 它一切正常但它似乎“脆弱”。我知道大类不是很好,但至少对于一个大文件中的所有内容,我似乎更清楚我要更新的属性等。
- 更新 -
更多信息:
不幸的是,我无法发布任何实际代码,因为它是专有的 - 如果我有时间的话,会尝试提供虚拟示例并将其粘贴。下面的评论之一提到将代码重构为步骤,这正是我所做的。这个类的目的最终是一件事 - 创建一个随机的事物列表 - 所以在这个类被调用的唯一公共方法中 - 我已经将每个"步骤&#34重构为1级的缩减;。每个步骤,无论是同一个类中的方法,还是将其分解为辅助类来执行子步骤是有意义的 - 它仍然需要访问在过程中构建的列表和简单的计数器变量跟踪统计数据。
- 更新 -
这是尝试在代码中显示类似内容:
public class RandomList(){
public int Id{get; set;}
public int Name{get; set;}
public int NumOfInvalidItems {get; set;}
public int NumOfFirstChunkItems{get; set;}
public int NumOfSecondChunkItems{get; set;}
public ICollection<RandomListItem> Items{get; set;}
}
public class CreateRandomListService(){
private readonly IUnitOfWork _unitOfWork;
private readonly ICreateRandomListValidator _createRandomListValidator;
private readonly IRandomSubProcessService _randomSubProcessService;
private readonly IAnotherSubProcessService _anotherSubProcessService;
private RandomList _randomList;
public CreateRandomListService(IUnitOfWork unitOfWork,
ICreateRandomListValidator createRandomListValidator,
IRandomFirstChunkFactory randomFirstChunkFactory,
IRandomSecondChunkFactory randomSecondChunkFactory){
_unitOfWork = unitOfWork;
_createRandomListValidator = createRandomListValidator;
_randomFirstChunkService = randomFirstChunkFactory.Create(_unitOfWork);
_randomSecondChunkService = randomSecondChunkFactory.Create(_unitOfWork);
}
public CreateResult CreateRandomList(CreateRandomListValues createValues){
// validate passed in model before proceeding
if(_createRandomListValidator.Validate(createValues))
return new CreateResult({HasErrors:true});
InitializeValues(createValues); // fetch settings from db etc and build up
ProcessFirstChunk();
ProcessSecondChunk();
SaveWithStatistics();
createResult.Id = _randomList.Id;
return createResult;
}
private InitializeValues(CreateRandomListValues createValues){
_createValues = createValues;
_createValues.ImportantSetting = _unitOfWork.SettingsRepository.GetImportantSetting();
// etc.
_randomList = new RandomList(){
// set initial properties etc. some come from the passed in createValues, some from db
}
}
private void ProcessFirstChunk(){
_randomFirstChunkService.GetRandomFirstChunk(_createValues);
}
private void ProcessSecondChunk(){
_randomSecondChunkService.GetRandomSecondChunk(_createValues);
}
private void SaveWithStatistics(){
_randomList.Items _createValues.ListOfItems;
_randomList.NumOfInvalidItems = _createValues.NumOfInvalidItems;
_randomList.NumOfItemsChosen = _createValues.NumOfItemsChosen;
_randomList.NumOfFirstChunkItems = _createValues.NumOfFirstChunkItems;
_randomList.NumOfSecondChunkItems = _createValues.NumOfSecondChunkItems;
_unitOfWork.RandomThingRepository.Add(_randomList);
_unitOfWork.Save();
}
}
public class RandomFirstChunkService(){
private IUnitOfWork _unitOfWork;
public RandomFirstChunkService(IUnitOfWork unitOfWork){
_unitOfWork = unitOfWork;
}
public void GetRandomFirstChunk(CreateRandomListValues createValues){
// do processing here - build up list collection and keep track of counts
CallMethodThatUpdatesList(creatValues);
// how to return this to calling class? currently just updating values in createValues by reference
// can also return a complex class here and extract the values back to the main class' member
// variables
}
private void CallMethodThatUpdatesList(createRandomListValues createValues){
// do work
}
}
答案 0 :(得分:1)
残酷的答案是,它当然取决于......在没有阅读代码的情况下很难找到答案,但我会说,一旦你创建了新的类(有一个目的),那些类和接口应该定义你需要传递哪些数据对象来解决你的问题。在这种情况下,一个方法返回相同的类型作为传递进入它是奇怪的,我还认为通过方法的seriers操作一个对象是脆弱的。想象一下,如果你们每个人都是一个REST服务;然后这些界面将如何。
答案 1 :(得分:0)
我不会“传递东西”。我也不会因为它的1000行而将它分解成单独的类。你最终会让它变得更加混乱,而且更多的是维护问题。
你没有发布你的代码(duh),所以很难批评它。如果你真的过去了,我怀疑你可能有重复的代码,可以重构为方法等。
如果您已经摆脱了重复的代码,我接下来将所有数据库内容都移到DAL层。
如果你真的想让它变小(基于你提供的小信息),我接下来将它重构为“步骤”并制作工作流类型父容器类。
再说一次,很难说不知道代码。
答案 2 :(得分:0)
我不知道你到目前为止如何设法重构这门课程,但从你的解释来看,它听起来就像是&#34;统计数据&#34;应该成为一个对象的概念,如:
interface IStatistic<TOutput>
{
IEnumerable<TOutput> Calculate(IEnumerable<input-type>);
}
当您希望显示某些统计信息时,只需使用相应的统计信息:
return new MySpecial().Calculate(myData);
如果统计对象不容易构建,e.i。他们要求一些参数,那么你可以提供一个创建它们的Func委托:
void DoSomething(Func<IStatistic<string>> factory)
{
string[] inputData = ...
foreach (string line in factory().Calculate(inputData))
{
// do something...
}
}
当你提到多个列表时,我认为输入类型实际上是几种输入类型。如果是这样,那么提供一种DTO来保存列表可能真的很有意义:
class RawData
{
public IEnumerable<type1> Data1 { get; }
public IEnumerabel<type2> Data2 { get; }
...
}
然而,请注意,这不是DTO&#34;书#34;。首先,它是不可变的 - 只有吸气剂存在。其次,它只公开序列(IEnumerable
),而不是原始列表。采取这两种措施是为了禁止统计对象操纵数据。