-
Notifications
You must be signed in to change notification settings - Fork 264
Якшибаев Данил #243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Якшибаев Данил #243
Changes from all commits
08636c2
8797728
f55388d
3a9eb7c
c7e4a10
aa52874
594b61a
b004477
cff7d59
b22a07e
42ffc9d
7a0b41e
c5ec289
5612173
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| namespace ObjectPrinting.HomeWork.Extensions; | ||
|
|
||
| public static class ObjectPrinterExtensions | ||
| { | ||
| public static string PrintToString<T>(this T obj) | ||
| { | ||
| return ObjectPrinter.For<T>().PrintToString(obj); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| using ObjectPrinting.HomeWork.PrintUtils; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.Extensions; | ||
|
|
||
| public static class PrintingConfigExtensions | ||
| { | ||
| public static string PrintToString<T>(this T obj, Func<PrintingConfig<T>, PrintingConfig<T>> config) | ||
| { | ||
| return config(ObjectPrinter.For<T>()).PrintToString(obj); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using ObjectPrinting.HomeWork.PrintUtils; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Implementations; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Strategies.Implementations; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Strategies.Interfaces; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Implementations; | ||
|
|
||
| namespace ObjectPrinting.HomeWork; | ||
|
|
||
| public class ObjectPrinter | ||
| { | ||
| public static PrintingConfig<T> For<T>() | ||
| { | ||
| var ruleProcessor = new RuleProcessor(); | ||
| var renderProperty = new PropertyRenderer(); | ||
| var strategies = new List<IPrintStrategy> | ||
| { | ||
| new EnumerablePrinterStrategy(), | ||
| new SimplePrinterStrategy(ruleProcessor), | ||
| new ObjectPrinterStrategy(renderProperty, ruleProcessor), | ||
| new CycleFormatterStrategy() | ||
| }; | ||
| return new PrintingConfig<T>(ruleProcessor, new PrintingProcessor(strategies)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| namespace ObjectPrinting.HomeWork.PrintUtils.Helpers; | ||
|
|
||
| public static class SimpleTypeHelper | ||
| { | ||
| public static bool IsSimple(Type type) | ||
| { | ||
| return type.IsPrimitive | ||
| || type == typeof(string) | ||
| || type == typeof(decimal) | ||
| || type == typeof(DateTime) | ||
| || type == typeof(Guid) | ||
| || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && | ||
| IsSimple(type.GetGenericArguments()[0])); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| using System.Text; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Interfaces; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Strategies.Implementations; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Strategies.Interfaces; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.PrintUtils.Implementations; | ||
|
|
||
| public class PrintingProcessor( | ||
| IEnumerable<IPrintStrategy> strategies) | ||
| : IPrintingProcessor | ||
| { | ||
| private readonly List<IPrintStrategy> strategies = strategies.ToList(); | ||
|
|
||
| public string Print(object? obj, int nestingLevel, HashSet<object> visited) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Зачем метод принимает
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nestingLevel и visited передаются в метод, потому что при рекурсивной печати объекта они меняются на каждом шаге There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Да, чёт не заметил |
||
| { | ||
| if (obj == null) return "null"; | ||
|
|
||
| var type = obj.GetType(); | ||
|
|
||
| var sb = new StringBuilder(); | ||
| foreach (var strategy in strategies.OrderByDescending(strategy => strategy is CycleFormatterStrategy)) | ||
| { | ||
| if (!strategy.CanHandle(type)) continue; | ||
|
|
||
| var result = strategy.Print(obj, nestingLevel, visited, Print, sb); | ||
| if (result != null) | ||
| { | ||
| return result; | ||
| } | ||
| } | ||
|
|
||
| throw new InvalidOperationException($"No strategy could print object of type {type.Name}"); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| using System.Collections; | ||
| using System.Reflection; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Helpers; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Interfaces; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Dto; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Interfaces; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.PrintUtils.Implementations; | ||
|
|
||
| public class PropertyRenderer : IPropertyRenderer | ||
| { | ||
| public string? RenderProperty( | ||
| object target, | ||
| PropertyInfo prop, | ||
| int nestingLevel, | ||
| HashSet<object> visited, | ||
| IRuleProcessor ruleProcessor, | ||
| Func<object?, int, HashSet<object>, string> recursivePrinter) | ||
| { | ||
| var value = prop.GetValue(target); | ||
| if (value == null) return null; | ||
|
|
||
| var ruleOutcome = ruleProcessor.ApplyRule(value, prop); | ||
| if (ruleOutcome.Action == RuleResult.Skip) return null; | ||
|
|
||
| var type = value.GetType(); | ||
|
|
||
| if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string) || !SimpleTypeHelper.IsSimple(type)) | ||
| { | ||
| var printed = recursivePrinter(value, nestingLevel + 1, visited); | ||
| return $"{prop.Name} =\n{printed}"; | ||
| } | ||
|
|
||
| var formatted = ruleOutcome.Value ?? value.ToString(); | ||
| return $"{prop.Name} = {formatted}"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| namespace ObjectPrinting.HomeWork.PrintUtils.Interfaces; | ||
|
|
||
| public interface IPrintingProcessor | ||
| { | ||
| string Print(object? obj, int nestingLevel, HashSet<object> visited); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| using System.Reflection; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Interfaces; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.PrintUtils.Interfaces; | ||
|
|
||
| public interface IPropertyRenderer | ||
| { | ||
| public string? RenderProperty( | ||
| object target, | ||
| PropertyInfo prop, | ||
| int nestingLevel, | ||
| HashSet<object> visited, | ||
| IRuleProcessor ruleProcessor, | ||
| Func<object?, int, HashSet<object>, string> recursivePrinter); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| using System.Globalization; | ||
| using System.Linq.Expressions; | ||
| using System.Reflection; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Interfaces; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Interfaces; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Strategies.Implementations; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.PrintUtils; | ||
|
|
||
| public class PrintingConfig<TOwner>(IRuleProcessor rp, IPrintingProcessor pr) | ||
| { | ||
| public string PrintToString(TOwner obj) => pr.Print(obj, 0, []); | ||
|
|
||
| public TypePrintingConfig<TOwner, TType> For<TType>() => new(rp, this); | ||
| public PropertyPrintingConfig<TOwner, TProp> For<TProp>(Expression<Func<TOwner, TProp>> selector) | ||
| => new(rp, this, selector); | ||
|
|
||
| public PrintingConfig<TOwner> Exclude<T>() | ||
| { | ||
| rp.AddRule(new ExcludeRule(typeof(T))); | ||
| return this; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Exclude<TProp>(Expression<Func<TOwner, TProp>> selector) | ||
| { | ||
| var prop = GetProperty(selector); | ||
| rp.AddRule(new ExcludeRule(prop)); | ||
| return this; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Trim(Expression<Func<TOwner, string>> selector, int length) | ||
| { | ||
| var prop = GetProperty(selector); | ||
| rp.AddRule(new TrimStringRule(prop, length)); | ||
| return this; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Serialize<TType>(Func<TType, string> serializer) | ||
| { | ||
| rp.AddRule(new SerializationRule<TType>(serializer)); | ||
| return this; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Serialize<TProp>(Expression<Func<TOwner, TProp>> selector, | ||
| Func<TProp, string> serializer) | ||
| { | ||
| var prop = GetProperty(selector); | ||
| rp.AddRule(new SerializationRule<TProp>(serializer, prop)); | ||
| return this; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> SetFormattingCulture(CultureInfo culture) | ||
| { | ||
| rp.AddRule(new CultureRule(culture)); | ||
| return this; | ||
| } | ||
|
|
||
| private static PropertyInfo GetProperty<T>(Expression<Func<TOwner, T>> expr) | ||
| => (PropertyInfo)((MemberExpression)expr.Body).Member; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| using System.Linq.Expressions; | ||
| using System.Reflection; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Interfaces; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Strategies.Implementations; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.PrintUtils; | ||
|
|
||
| public class PropertyPrintingConfig<TOwner, TProp>( | ||
| IRuleProcessor rules, | ||
| PrintingConfig<TOwner> parent, | ||
| Expression<Func<TOwner, TProp>> selector) | ||
| { | ||
| private readonly PropertyInfo property = GetProperty(selector); | ||
|
|
||
| public PropertyPrintingConfig<TOwner, TProp> Serialize(Func<TProp, string> serializer) | ||
| { | ||
| rules.AddRule(new SerializationRule<TProp>(serializer, property)); | ||
| return this; | ||
| } | ||
|
|
||
| public PropertyPrintingConfig<TOwner, string> Trim(int length) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Я могу вызвать For для |
||
| { | ||
| if (typeof(TProp) != typeof(string)) | ||
| throw new InvalidOperationException("Trim доступен только для строк."); | ||
|
|
||
| rules.AddRule(new TrimStringRule(property, length)); | ||
| return (PropertyPrintingConfig<TOwner, string>)(object)this; | ||
| } | ||
|
|
||
| public PropertyPrintingConfig<TOwner, TProp> Exclude() | ||
| { | ||
| rules.AddRule(new ExcludeRule(property)); | ||
| return this; | ||
| } | ||
|
|
||
| public PrintingConfig<TOwner> Apply() => parent; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Можно было этого избежать добавив сюда методы из PrintingConfig. |
||
|
|
||
| private static PropertyInfo GetProperty<T>(Expression<Func<TOwner, T>> exp) => | ||
| (PropertyInfo)((MemberExpression)exp.Body).Member; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| using System.Text; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Strategies.Interfaces; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.PrintUtils.Strategies.Implementations; | ||
|
|
||
| public class CycleFormatterStrategy : IPrintStrategy | ||
| { | ||
| private readonly Dictionary<object, int> visited = new(); | ||
| public bool CanHandle(Type type) => !type.IsValueType && type != typeof(string); | ||
|
|
||
|
|
||
| public string Print(object obj, int nestingLevel, HashSet<object> ignoredVisited, | ||
| Func<object?, int, HashSet<object>, string> ignoredRecursivePrinter, StringBuilder sb) | ||
| { | ||
| if (visited.TryGetValue(obj, out var originalLevel)) | ||
| { | ||
| return FormatReference(obj, originalLevel); | ||
| } | ||
|
|
||
| visited[obj] = nestingLevel; | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private string FormatReference(object obj, int nestingLevel) | ||
| { | ||
| var type = obj.GetType(); | ||
| return $"<see {type.Name} level={nestingLevel}>"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| using System.Collections; | ||
| using System.Text; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Strategies.Interfaces; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.PrintUtils.Strategies.Implementations; | ||
|
|
||
| public class EnumerablePrinterStrategy : IPrintStrategy | ||
| { | ||
| public bool CanHandle(Type type) => typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string); | ||
|
|
||
| public string Print(object obj, int nestingLevel, HashSet<object> visited, | ||
| Func<object?, int, HashSet<object>, string> recursivePrinter, StringBuilder sb) | ||
| { | ||
| return PrintEnumerable((IEnumerable)obj, nestingLevel, visited, recursivePrinter, sb); | ||
| } | ||
|
|
||
| private string PrintEnumerable(IEnumerable enumerable, int nestingLevel, HashSet<object> visited, | ||
| Func<object?, int, HashSet<object>, string> recursivePrinter, StringBuilder sb) | ||
| { | ||
| var indent = new string('\t', nestingLevel); | ||
|
|
||
| sb.AppendLine(indent + enumerable.GetType().Name + " ["); | ||
|
|
||
| foreach (var item in enumerable) | ||
| { | ||
| var itemText = recursivePrinter(item, nestingLevel, visited); | ||
|
|
||
| var itemLines = itemText.Split(Environment.NewLine); | ||
| foreach (var line in itemLines) | ||
| { | ||
| sb.AppendLine(new string('\t', nestingLevel + 1) + line); | ||
| } | ||
| } | ||
|
|
||
| sb.AppendLine(indent + "]"); | ||
| return sb.ToString(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| using System.Collections; | ||
| using System.Reflection; | ||
| using System.Text; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Helpers; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Interfaces; | ||
| using ObjectPrinting.HomeWork.PrintUtils.Strategies.Interfaces; | ||
| using ObjectPrinting.HomeWork.RuleUtils.Interfaces; | ||
|
|
||
| namespace ObjectPrinting.HomeWork.PrintUtils.Strategies.Implementations; | ||
|
|
||
| public class ObjectPrinterStrategy(IPropertyRenderer propertyRenderer, IRuleProcessor ruleProcessor) : IPrintStrategy | ||
| { | ||
| public bool CanHandle(Type type) => !typeof(IEnumerable).IsAssignableFrom(type) && !SimpleTypeHelper.IsSimple(type); | ||
|
|
||
| public string Print(object obj, int nestingLevel, HashSet<object> visited, | ||
| Func<object?, int, HashSet<object>, string> recursivePrinter, StringBuilder sb) | ||
| { | ||
| return PrintObject(obj, nestingLevel, visited, recursivePrinter, ruleProcessor, propertyRenderer, sb); | ||
| } | ||
|
|
||
| private string PrintObject( | ||
| object obj, | ||
| int nestingLevel, | ||
| HashSet<object> visited, | ||
| Func<object?, int, HashSet<object>, string> recursivePrinter, | ||
| IRuleProcessor ruleProcessor, | ||
| IPropertyRenderer propertyRenderer, | ||
| StringBuilder sb) | ||
| { | ||
| var type = obj.GetType(); | ||
| sb.AppendLine(type.Name); | ||
| var indent = new string('\t', nestingLevel + 1); | ||
|
|
||
| foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) | ||
| { | ||
| var propLine = propertyRenderer.RenderProperty( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. кажется, что PropertyRenderer усложняет код. Почему его работу нельзя доверить вызову
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. я придерживался принципа srp, код так стал более гибким и в дальнейшем этот PropertyRender можно будет переиспользовать без проблем. у него одна ответственность. а recursivePrinter занимается выводом объекта. если объединить, то мы потеряем гибгость и в дальнейшем не будет ясно почему метод propertyrender лежит в recursivePrint |
||
| obj, prop, nestingLevel, visited, | ||
| ruleProcessor, recursivePrinter | ||
| ); | ||
|
|
||
| if (!string.IsNullOrEmpty(propLine)) | ||
| { | ||
| sb.AppendLine(indent + propLine); | ||
| } | ||
| } | ||
|
|
||
| return sb.ToString().TrimEnd(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Почему он принимает стратегии извне? Не лучше бы было здесь их сразу и указывать? Пользователь же всё равно их нигде не конфигурирует.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DIP и DI. легче в тестируемости, в том числе, если добавятся новые стратегии