我的问题是当我有对象列表时,我应该将整个列表传递给另一层中的方法,还是应该有另一个模型并制作新的列表?
namespace Model
{
Public Class class2
{
blah blah properties
}
Public Class Person
{
public string firstname { get; set; }
public string lastname {get; set;}
public int age {get; set;}
public Datetime regDate {get; set;}
public class2 SomeClass{get; set;}
}
}
Namespace Service
{
Namespace Model
{
public class LogPerson
{
public string firstName {get; set;}
public string lastName {get; set;}
public int age {get; set;}
}
}
public static class Log
{
//Method1
public void Log(List<Person> persons)
{
LogProvider.Log(Persons.Select(p=> new LogPerson{ p.firstName, p.lastname, p.age}).ToList());
}
//Method2
public void Log(List<LogPerson> persons)
{
LogProvider.Log(persons);
}
}
正如我上面所示,传递完整的人员对象列表(日志提供者只需要名字,姓氏和年龄)或使用从一开始就需要LogPerson对象的方法2是一种好习惯。
我不需要意见,我需要的是,根据关注点的分离,哪一个是正确的方式?
答案 0 :(得分:2)
如果您定义的接口具有您需要记录的属性,则只能有一个模型:
// keep in a separate, shared assembly
public interface ILoggablePerson {
string firstName {get; set;}
string lastName {get; set;}
int age {get; set;}
}
然后
public void Log(IEnumerable<ILoggablePerson> people)
{
LogProvider.Log(persons);
}
(注意使用IEnumerable而不是List:这意味着参数可以延迟评估[如果需要]并且不会将方法使用者与特定数据结构联系起来)
最后,您的Person类只需将该接口添加到其声明:
public class Person : ILoggablePerson
然后你可以将Person对象传递给你的记录器类:
Log.Log(new Person[] { ... }); // etc.
修改以下评论
以下是我如何解决这个问题的一个例子:接口仍然提供关注点分离,只使用依赖注入,提供者和工厂模式......注释内联,希望它有所帮助。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Service;
using Model;
using System.Collections.Concurrent;
namespace ConsoleApplication8
{
class Program
{
static void Main(string[] args)
{
var carLogger = Logger.Default(new Model.CarLogEntryProvider()); // generic type inference
// want to log to a file? => Logger.File(@"c:\file path", new Model.CarLogEntryProvider());
var personLogger = Logger.Default(new Model.PersonLogger());
Car c1 = new Car() { Make = "Toyota", Registration = "ABC123" };
Car c2 = new Car() { Make = "Toyota", Registration = "ABX127" };
carLogger.AddEntries(new Car[] { c1, c2 });
Person p1 = new Person() { Age = 21, FirstName = "Tony", LastName = "Baloney" };
Person p2 = new Person() { Age = 31, FirstName = "Mary", LastName = "O'Doherty" };
personLogger.AddEntry(p1);
personLogger.AddEntry(p2);
Console.ReadLine();
}
}
}
// model namespace knows how the model works and can make the decision of how its types are logged
// by implementing ILogEntryProvider as required: can even combine fields, add additional fields (eg timestamp) etc.
namespace Model
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public class Car
{
public string Make { get; set; }
public string Registration { get; set; }
}
// knows how to log a Car as required by this namespace: another system can implement this differently
class CarLogEntryProvider : ILogEntryProvider<Car>
{
public ILogEntry GetLogEntry(Car car)
{
var result = new BasicLogEntry(); // can use a ThreadSafeLogEntry if application is multi-threaded
result.Values["make"] = car.Make;
result.Values["reg"] = car.Registration;
return result;
}
}
// knows how to log a Car as required by this namespace: another system can implement this differently
class PersonLogger : ILogEntryProvider<Person>
{
public ILogEntry GetLogEntry(Person person)
{
var result = new BasicLogEntry(); // can use a ThreadSafeLogEntry if application is multi-threaded
result.Values["age"] = person.Age.ToString();
result.Values["surname"] = person.LastName;
return result;
}
}
}
// service namespace has no knowledge of the model, it just provides interfaces for the model to provide
namespace Service
{
public interface ILogEntry {
IDictionary<string, string> Values { get; }
}
public interface ILogEntryProvider<T>
{
// can add any other properties here for fields which are always required
ILogEntry GetLogEntry(T itemToLog);
}
public class ThreadSafeLogEntry : ILogEntry
{
public ThreadSafeLogEntry() { Values = new ConcurrentDictionary<string, string>(); }
public IDictionary<string, string> Values
{
get;
set;
}
}
public class BasicLogEntry : ILogEntry
{
public BasicLogEntry() { Values = new Dictionary<string, string>(); }
public IDictionary<string, string> Values
{
get;
set;
}
}
public interface ILogger<T>
{
void AddEntry(T item);
void AddEntries(IEnumerable<T> items);
}
// factory pattern
public static class Logger
{
public static ILogger<T> Default<T>(ILogEntryProvider<T> entryProvider)
{
return new ConsoleLogger<T>(entryProvider);
}
// create other methods here as required, all returning type ILogger<T>
// eg: public static ILogger<T> File(string filePath, ILogEntryProvider<T> entryProvider) { ... }
}
class ConsoleLogger<T> : ILogger<T>
{
private ILogEntryProvider<T> logEntryProvider;
public ConsoleLogger(ILogEntryProvider<T> logEntryProvider) // dependency injection
{
if (logEntryProvider == null)
throw new ArgumentNullException();
this.logEntryProvider = logEntryProvider;
}
void ILogger<T>.AddEntry(T item) // explicit interface implementation: discourage use of this class in a fashion which doesn't treat it as an interface type
{
((ILogger<T>)this).AddEntries(new T[] { item });
}
void ILogger<T>.AddEntries(IEnumerable<T> items) // explicit interface implementation: discourage use of this class in a fashion which doesn't treat it as an interface type
{
var entries = items.Select(item => logEntryProvider.GetLogEntry(item))
.Where(anyEntry => anyEntry != null) // perhaps a different behaviour required here...
.Select(nonNullEntry => nonNullEntry.Values);
foreach(var entry in entries)
{
Console.WriteLine("New Entry: {0}", typeof(T).Name);
foreach(var property in entry.Keys)
{
// record each string pair etc. etc
string propertyValue = entry[property];
Console.WriteLine("[{0}] = \"{1}\"", property, propertyValue);
}
Console.WriteLine();
}
}
// TO DO: create an async pattern method:
// public static Task AddEntryAsync<T>(ILogEntryProvider<T> adapterFunc, IEnumerable<T> items) { .... }
}
}
答案 1 :(得分:0)
如果您想要分离关注点的答案,如果您不需要传递的属性,那么就可以创建另一个模型和新列表了。
这将分离各层的关注点。第2层不关心第1层不需要的属性。