'어떠한 행위' 앞, 뒤 혹은 중간중간에 반복적으로 수행하는 코드들이 존재한다. 그 코드들을 일반화한다. 부모클래스에서 반복적으로 하는 일들을 정의하고 파생클래스에서 '어떠한 행위'를 정의하는 것을 약속한다. 주로 라이브러리를 만들 때 자주 사용하는 패턴이다.
다음 예제는 클래스가 갖고있는 숫자, 혹은 문자열 프로퍼티를 텍스트 파일에 쓰거나 읽을 수 있는 인스턴스가 있고 인스턴스는 파일을 열고 닫을 때 Json 파일로 직렬화, 역직렬화 하는 반복적인 행위는 기반클래스에서 정의하여 반복적인 코드를 줄인다.
이름(string)과 나이(integer)를 프로퍼티로 갖고있는 Person 클래스가 있고 우선 Person 클래스를 문자열만 쓸 수 있는 인스턴스를 통해 이름을 쓰고, 숫자만 쓸 수 있는 인스턴스를 통해 나이를 쓰는데 숫자는 30 미만만 쓰도록 한다. 마지막으로는 Json으로 쓰여진 파일을 열어서 문자, 숫자만을 각각 읽어온다.
public static int Seq { get; private set; } = 1;
public abstract class BaseTexter<T, U> where T : class
{
public string TargetFile
{
get; private set;
}
public BaseTexter(string targetFile)
{
this.TargetFile = targetFile;
}
public void Write(T target)
{
var values = GetValues(target);
if (values.Count == 0)
return;
var serialize = JsonConvert.SerializeObject(values);
JObject root = ReadAll(this.TargetFile);
root.Add(string.Format("{0:D2}", Seq), JToken.Parse(serialize));
using (FileStream fileStream = File.Create(TargetFile))
{
using (JsonTextWriter writer = new JsonTextWriter(new StreamWriter(fileStream)))
{
root.WriteTo(writer);
}
}
Seq++;
}
public static JObject ReadAll(string targetFile)
{
if (!File.Exists(targetFile))
using (File.Create(targetFile)) { }
using (FileStream fileStream = File.Open(targetFile, FileMode.Open, FileAccess.Read))
using (StreamReader reader = new StreamReader(fileStream))
{
return JsonConvert.DeserializeObject<JObject>(reader.ReadToEnd()) ?? new JObject();
}
}
public Dictionary<string, U> GetValues(T target)
{
Dictionary<string, U> nameValuePair = new Dictionary<string, U>();
foreach (var prop in target?.GetType().GetProperties())
{
if (prop.PropertyType == ValueType())
{
var value = (U)prop.GetValue(target);
if (Where(value))
{
nameValuePair.Add(prop.Name, value);
Console.WriteLine($"{prop.Name}: {value} add");
}
else
Console.WriteLine($"{prop.Name}: {value} skip");
}
}
return nameValuePair;
}
public void Read()
{
JObject root = ReadAll(this.TargetFile);
foreach(var childProp in root.Properties())
{
foreach(var values in childProp.Children())
{
foreach (var value in values.ToObject<JObject>().Properties())
{
if(TypeCompare(value.Value.Type))
Console.WriteLine($"{childProp.Name}: prop: {value.Name} value: {value.Value}");
}
}
}
}
public abstract Type ValueType();
public abstract bool Where(U value);
public abstract bool TypeCompare(JTokenType type);
}
public class StringTexter<T> : BaseTexter<T, string> where T : class
{
public StringTexter(string targetFile) : base(targetFile)
{
}
public override bool TypeCompare(JTokenType type) => type == JTokenType.String;
public override Type ValueType() => typeof(string);
public override bool Where(string value) => true;
}
public class IntTexter<T> : BaseTexter<T, int> where T : class
{
public IntTexter(string targetFile) : base(targetFile)
{
}
public override bool TypeCompare(JTokenType type) => type == JTokenType.Integer;
public override Type ValueType() => typeof(int);
public override bool Where(int value)
{
if (value < 30)
return true;
else
return false;
}
}
public class Person
{
public Person(string name, int age)
{
Name = name;
Age = age;
}
public string Name { get; private set; }
public int Age { get; private set; }
}
static void Main(string[] args)
{
StringTexter<Person> stringTexter = new StringTexter<Person>(@"C:\CSharp\Study\200224\Pattern\TemplateMethod\jsonText.txt");
IntTexter<Person> intTexter = new IntTexter<Person>(@"C:\CSharp\Study\200224\Pattern\TemplateMethod\jsonText.txt");
List<Person> people = new List<Person>()
{
new Person("최용국", 28),
new Person("이은혜", 31),
new Person("안광필", 35),
new Person("이정선", 29)
};
foreach(var person in people)
{
stringTexter.Write(person);
}
foreach(var person in people)
{
intTexter.Write(person);
}
Console.WriteLine("================integer============");
intTexter.Read();
Console.WriteLine("================string============");
stringTexter.Read();
}