簡體   English   中英

工廠設計模式困境

[英]Factory design pattern woes

所以我有這個工廠類,我認為它應該是一個靜態工廠類應該是:

public class FileFactory
{
    public static File Create(IObjectService service, string destination, string fileName, string mimeType)
    {
        var type = mimeType.ToLower().Split('/')[0];

        switch(type)
        {
            case "image":
                return new Image(service, destination, fileName);
            case "document":
                return new Document(service, destination, fileName);
            default:
                return new Document(service, destination, fileName);
        }
    }
}

現在,我有另一個不是靜態的類,而且復雜得多:

public class MetadataFactory
{
    public string destination;
    public string fileName;
    public string fullPath;

    private XDocument document;

    private XNamespace SystemNamespace = "http://ns.exiftool.ca/File/System/1.0/";
    private XNamespace FileNamespace = "http://ns.exiftool.ca/File/1.0/";
    private XNamespace Composite = "http://ns.exiftool.ca/Composite/1.0/";
    private XNamespace PNG = "http://ns.exiftool.ca/PNG/PNG/1.0/";
    private XNamespace GIF = "http://ns.exiftool.ca/GIF/GIF/1.0/";
    private XNamespace IFD0 = "http://ns.exiftool.ca/EXIF/IFD0/1.0/";
    private XNamespace IFD1 = "http://ns.exiftool.ca/EXIF/IFD1/1.0/";
    private XNamespace BMP = "http://ns.exiftool.ca/BMP/BMP/1.0/";
    private XNamespace JFIF = "http://ns.exiftool.ca/JFIF/JFIF/1.0/";
    private XNamespace XMPtiff = "http://ns.exiftool.ca/XMP/XMP-tiff/1.0/";
    private XNamespace XMPxmp = "http://ns.exiftool.ca/XMP/XMP-xmp/1.0/";
    private XNamespace PDF = "http://ns.exiftool.ca/PDF/PDF/1.0/";
    private XNamespace FlashPix = "http://ns.exiftool.ca/FlashPix/FlashPix/1.0/";
    private XNamespace XML = "http://ns.exiftool.ca/XML/XML/1.0/";

    public Metadata Create(string destination, string fileName, string exifToolPath)
    {
        this.destination = destination;
        this.fileName = fileName;
        this.fullPath = Path.Combine(destination, fileName);
        this.document = new XDocument(GetFullXml(exifToolPath));

        var mime = (string)this.document.Descendants(FileNamespace + "MIMEType").FirstOrDefault();
        var type = mime.ToLower().Split('/')[0];

        var metadata = new Metadata()
        {
            ReferenceId = this.GenerateId(),

            FileSize = (string)this.document.Descendants(SystemNamespace + "FileSize").FirstOrDefault(),
            FileType = (string)this.document.Descendants(FileNamespace + "FileType").FirstOrDefault(),
            MIMEType = (string)this.document.Descendants(FileNamespace + "MIMEType").FirstOrDefault(),
        };

        switch (type)
        {
            case "image":

                metadata.CreateDate = this.GetCreateDate();
                metadata.ModifyDate = this.GetModifyDate();

                metadata.ImageWidth = this.GetImageWidth();
                metadata.ImageHeight = this.GetImageHeight();
                metadata.ImageSize = this.GetImageSize();

                metadata.Orientation = (string)this.document.Descendants(XMPtiff + "Orientation").FirstOrDefault();

                break;

            case "document":

                metadata.CreateDate = this.GetCreateDate();
                metadata.ModifyDate = this.GetModifyDate();

                break;
        }

        return metadata;
    }        

    private XElement GetFullXml(string exifToolPath)
    {
        string args = string.Format("-X \"{0}\"", this.fullPath);
        string output = RunProcess(exifToolPath, args);
        output = Sanitize(output);

        return new XElement("FullMetadata", XElement.Parse(output));
    }

    private virtual string GetCreateDate()
    {
        if (this.document.Descendants(XMPxmp + "CreateDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(XMPxmp + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(PDF + "CreateDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(PDF + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(XMPxmp + "CreateDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(XMPxmp + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(FlashPix + "CreateDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(FlashPix + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(XML + "CreateDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(XML + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(Composite + "DateTimeCreated").FirstOrDefault() != null)
            return (string)this.document.Descendants(Composite + "DateTimeCreated").FirstOrDefault();

        return null;
    }

    private virtual string GetModifyDate()
    {
        if (this.document.Descendants(IFD0 + "ModifyDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(IFD0 + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(PDF + "ModifyDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(PDF + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(FlashPix + "ModifyDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(FlashPix + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(XML + "ModifyDate").FirstOrDefault() != null)
            return (string)this.document.Descendants(XML + "ModifyDate").FirstOrDefault();

        return null;
    }

    private virtual string GetDuration()
    {
        if (this.document.Descendants(Composite + "Duration").FirstOrDefault() != null)
            return (string)this.document.Descendants(Composite + "Duration").FirstOrDefault();

        return null;
    }

    private virtual int GetImageWidth()
    {
        if (this.document.Descendants(PNG + "ImageWidth").FirstOrDefault() != null)
            return (int)this.document.Descendants(PNG + "ImageWidth").FirstOrDefault();

        if (this.document.Descendants(GIF + "ImageWidth").FirstOrDefault() != null)
            return (int)this.document.Descendants(GIF + "ImageWidth").FirstOrDefault();

        if (this.document.Descendants(IFD0 + "ImageWidth").FirstOrDefault() != null)
            return (int)this.document.Descendants(IFD0 + "ImageWidth").FirstOrDefault();

        if (this.document.Descendants(BMP + "ImageWidth").FirstOrDefault() != null)
            return (int)this.document.Descendants(BMP + "ImageWidth").FirstOrDefault();

        if (this.document.Descendants(FileNamespace + "ImageWidth").FirstOrDefault() != null)
            return (int)this.document.Descendants(FileNamespace + "ImageWidth").FirstOrDefault();

        return 0;
    }

    private virtual int GetImageHeight()
    {
        if (this.document.Descendants(PNG + "ImageHeight").FirstOrDefault() != null)
            return (int)this.document.Descendants(PNG + "ImageHeight").FirstOrDefault();

        if (this.document.Descendants(GIF + "ImageHeight").FirstOrDefault() != null)
            return (int)this.document.Descendants(GIF + "ImageHeight").FirstOrDefault();

        if (this.document.Descendants(IFD0 + "ImageHeight").FirstOrDefault() != null)
            return (int)this.document.Descendants(IFD0 + "ImageHeight").FirstOrDefault();

        if (this.document.Descendants(BMP + "ImageHeight").FirstOrDefault() != null)
            return (int)this.document.Descendants(BMP + "ImageHeight").FirstOrDefault();

        if (this.document.Descendants(FileNamespace + "ImageHeight").FirstOrDefault() != null)
            return (int)this.document.Descendants(FileNamespace + "ImageHeight").FirstOrDefault();

        return 0;
    }

    private virtual string GetImageSize()
    {
        if (this.document.Descendants(PNG + "ImageSize").FirstOrDefault() != null)
            return (string)this.document.Descendants(PNG + "ImageSize").FirstOrDefault();

        if (this.document.Descendants(GIF + "ImageSize").FirstOrDefault() != null)
            return (string)this.document.Descendants(GIF + "ImageSize").FirstOrDefault();

        if (this.document.Descendants(IFD0 + "ImageSize").FirstOrDefault() != null)
            return (string)this.document.Descendants(IFD0 + "ImageSize").FirstOrDefault();

        if (this.document.Descendants(BMP + "ImageSize").FirstOrDefault() != null)
            return (string)this.document.Descendants(BMP + "ImageSize").FirstOrDefault();

        if (this.document.Descendants(Composite + "ImageSize").FirstOrDefault() != null)
            return (string)this.document.Descendants(Composite + "ImageSize").FirstOrDefault();

        return null;
    }

    private string GenerateId()
    {
        long i = 1;
        foreach (byte b in Guid.NewGuid().ToByteArray())
        {
            i *= ((int)b + 1);
        }
        return string.Format("{0:x}", i - DateTime.Now.Ticks);
    }

    private string RunProcess(string exifToolPath, string args)
    {
        if (String.IsNullOrEmpty(exifToolPath))
            throw new SystemException("EXIFTool Executable Path Not Configured");

        if (!System.IO.File.Exists(exifToolPath))
            throw new SystemException("EXIFTool Executable Not Found: " + exifToolPath);

        var process = new Process
        {
            StartInfo =
            {
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                FileName = exifToolPath,
                Arguments = args
            }
        };

        process.Start();

        var output = process.StandardOutput.ReadToEnd();
        process.WaitForExit();

        return output;
    }

    private string Sanitize(string s)
    {
        return s.Replace("&", string.Empty);
    }
}

我把這兩個工廠稱為:

var metadata = new MetadataFactory().Create(this.uploadPath, asset.FileName, this.exifToolPath);
var file = FileFactory.Create(objectService, this.uploadPath, asset.FileName, metadata.MIMEType);

現在,問題是這個。 FileFactory返回從File繼承的DocumentImage 對我來說,這是建造工廠的正確方法。

MetadataFactory,另一方面只返回元數據 ,它只能通過使用的Exif文件解壓得到它的元數據。 所以,我的問題是:工廠是否是正確的模式,或者我應該考慮做其他事情?

只是為了澄清一些事情。

元數據是POCO類元數據只能通過運行使用Exif提取xml數據的進程來構建

如果可以,請你幫助我 :)

所以我有這個工廠類,我相信它應該是一個靜態工廠類

沒有這樣的模式。 只有“工廠方法”和“抽象工廠”。 您可能有一個具有靜態工廠方法的類,但不具有靜態工廠類。

第二類在概念上更像是Builder而不是Factory方法。 工廠應該是簡單的方法,只有一個目的 - 根據提供的批准返回一個對象。 構建器更適合構建復雜對象。

所以,我采用了Builder Design Pattern (由Ondrej建議)並將其應用於我的元數據。 這就是它的方式。

首先,我創建了我的IMetadataBuilder接口:

interface IMetadataBuilder
{
    void SetCreateDate();
    void SetModifyDate();
    void SetImageWidth();
    void SetImageHeight();
    void SetImageSize();
    Metadata GetMetadata();
}

非常直接,然后從那里我創建了我的DocumentMetadataBuilderImageMetadataBuilder,如下所示:

public class DocumentMetadataBuilder : IMetadataBuilder
{
    private readonly Metadata metadata;
    private readonly XDocument document;

    private XNamespace Composite = "http://ns.exiftool.ca/Composite/1.0/";
    private XNamespace XMPxmp = "http://ns.exiftool.ca/XMP/XMP-xmp/1.0/";
    private XNamespace PDF = "http://ns.exiftool.ca/PDF/PDF/1.0/";
    private XNamespace FlashPix = "http://ns.exiftool.ca/FlashPix/FlashPix/1.0/";
    private XNamespace XML = "http://ns.exiftool.ca/XML/XML/1.0/";

    public DocumentMetadataBuilder(XDocument document, string fileSize, string fileType, string mimeType)
    {
        this.document = document;
        this.metadata = new Metadata()
        {
            FileSize = fileSize,
            FileType = fileType,
            MIMEType = mimeType
        };
    }

    public void SetCreateDate()
    {
        var createDate = string.Empty;

        if (this.document.Descendants(PDF + "CreateDate").FirstOrDefault() != null)
            createDate = (string)this.document.Descendants(PDF + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(XMPxmp + "CreateDate").FirstOrDefault() != null)
            createDate = (string)this.document.Descendants(XMPxmp + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(FlashPix + "CreateDate").FirstOrDefault() != null)
            createDate = (string)this.document.Descendants(FlashPix + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(XML + "CreateDate").FirstOrDefault() != null)
            createDate = (string)this.document.Descendants(XML + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(Composite + "DateTimeCreated").FirstOrDefault() != null)
            createDate = (string)this.document.Descendants(Composite + "DateTimeCreated").FirstOrDefault();

        this.metadata.CreateDate = createDate;
    }

    public void SetModifyDate()
    {
        var modifyDate = string.Empty;

        if (this.document.Descendants(PDF + "ModifyDate").FirstOrDefault() != null)
            modifyDate = (string)this.document.Descendants(PDF + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault() != null)
            modifyDate = (string)this.document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(FlashPix + "ModifyDate").FirstOrDefault() != null)
            modifyDate = (string)this.document.Descendants(FlashPix + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(XML + "ModifyDate").FirstOrDefault() != null)
            modifyDate = (string)this.document.Descendants(XML + "ModifyDate").FirstOrDefault();

        this.metadata.ModifyDate = modifyDate;
    }

    public void SetImageWidth()
    {
        throw new NotImplementedException();
    }

    public void SetImageHeight()
    {
        throw new NotImplementedException();
    }

    public void SetImageSize()
    {
        throw new NotImplementedException();
    }

    public Metadata GetMetadata()
    {
        throw new NotImplementedException();
    }
}

public class ImageMetadataBuilder : IMetadataBuilder
{
    private readonly Metadata metadata;
    private readonly XDocument document;

    private XNamespace SystemNamespace = "http://ns.exiftool.ca/File/System/1.0/";
    private XNamespace FileNamespace = "http://ns.exiftool.ca/File/1.0/";
    private XNamespace Composite = "http://ns.exiftool.ca/Composite/1.0/";
    private XNamespace PNG = "http://ns.exiftool.ca/PNG/PNG/1.0/";
    private XNamespace GIF = "http://ns.exiftool.ca/GIF/GIF/1.0/";
    private XNamespace IFD0 = "http://ns.exiftool.ca/EXIF/IFD0/1.0/";
    private XNamespace IFD1 = "http://ns.exiftool.ca/EXIF/IFD1/1.0/";
    private XNamespace BMP = "http://ns.exiftool.ca/BMP/BMP/1.0/";
    private XNamespace JFIF = "http://ns.exiftool.ca/JFIF/JFIF/1.0/";
    private XNamespace XMPtiff = "http://ns.exiftool.ca/XMP/XMP-tiff/1.0/";
    private XNamespace XMPxmp = "http://ns.exiftool.ca/XMP/XMP-xmp/1.0/";
    private XNamespace PDF = "http://ns.exiftool.ca/PDF/PDF/1.0/";
    private XNamespace FlashPix = "http://ns.exiftool.ca/FlashPix/FlashPix/1.0/";
    private XNamespace XML = "http://ns.exiftool.ca/XML/XML/1.0/";

    public ImageMetadataBuilder(XDocument document, string fileSize, string fileType, string mimeType)
    {
        this.document = document;
        this.metadata = new Metadata()  {
            FileSize = fileSize,
            FileType = fileType,
            MIMEType = mimeType
        };
    }

    public void SetCreateDate()
    {
        var createDate = string.Empty;

        if (this.document.Descendants(XMPxmp + "CreateDate").FirstOrDefault() != null)
            createDate = (string)this.document.Descendants(XMPxmp + "CreateDate").FirstOrDefault();

        if (this.document.Descendants(Composite + "DateTimeCreated").FirstOrDefault() != null)
            createDate = (string)this.document.Descendants(Composite + "DateTimeCreated").FirstOrDefault();

        this.metadata.CreateDate = createDate;
    }

    public void SetModifyDate()
    {
        var modifyDate = string.Empty;

        if (this.document.Descendants(IFD0 + "ModifyDate").FirstOrDefault() != null)
            modifyDate = (string)this.document.Descendants(IFD0 + "ModifyDate").FirstOrDefault();

        if (this.document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault() != null)
            modifyDate = (string)this.document.Descendants(XMPxmp + "ModifyDate").FirstOrDefault();

        this.metadata.ModifyDate = modifyDate;
    }

    public void SetImageWidth()
    {
        var imageWidth = 0;

        if (this.document.Descendants(PNG + "ImageWidth").FirstOrDefault() != null)
            imageWidth = (int)this.document.Descendants(PNG + "ImageWidth").FirstOrDefault();

        if (this.document.Descendants(GIF + "ImageWidth").FirstOrDefault() != null)
            imageWidth = (int)this.document.Descendants(GIF + "ImageWidth").FirstOrDefault();

        if (this.document.Descendants(IFD0 + "ImageWidth").FirstOrDefault() != null)
            imageWidth = (int)this.document.Descendants(IFD0 + "ImageWidth").FirstOrDefault();

        if (this.document.Descendants(BMP + "ImageWidth").FirstOrDefault() != null)
            imageWidth = (int)this.document.Descendants(BMP + "ImageWidth").FirstOrDefault();

        if (this.document.Descendants(FileNamespace + "ImageWidth").FirstOrDefault() != null)
            imageWidth = (int)this.document.Descendants(FileNamespace + "ImageWidth").FirstOrDefault();

        this.metadata.ImageWidth = imageWidth;
    }

    public void SetImageHeight()
    {
        var imageHeight = 0;

        if (this.document.Descendants(PNG + "ImageHeight").FirstOrDefault() != null)
            imageHeight = (int)this.document.Descendants(PNG + "ImageHeight").FirstOrDefault();

        if (this.document.Descendants(GIF + "ImageHeight").FirstOrDefault() != null)
            imageHeight = (int)this.document.Descendants(GIF + "ImageHeight").FirstOrDefault();

        if (this.document.Descendants(IFD0 + "ImageHeight").FirstOrDefault() != null)
            imageHeight = (int)this.document.Descendants(IFD0 + "ImageHeight").FirstOrDefault();

        if (this.document.Descendants(BMP + "ImageHeight").FirstOrDefault() != null)
            imageHeight = (int)this.document.Descendants(BMP + "ImageHeight").FirstOrDefault();

        if (this.document.Descendants(FileNamespace + "ImageHeight").FirstOrDefault() != null)
            imageHeight = (int)this.document.Descendants(FileNamespace + "ImageHeight").FirstOrDefault();

        this.metadata.ImageHeight = imageHeight;
    }

    public void SetImageSize()
    {
        var imageSize = string.Empty;

        if (this.document.Descendants(PNG + "ImageSize").FirstOrDefault() != null)
            imageSize = (string)this.document.Descendants(PNG + "ImageSize").FirstOrDefault();

        if (this.document.Descendants(GIF + "ImageSize").FirstOrDefault() != null)
            imageSize = (string)this.document.Descendants(GIF + "ImageSize").FirstOrDefault();

        if (this.document.Descendants(IFD0 + "ImageSize").FirstOrDefault() != null)
            imageSize = (string)this.document.Descendants(IFD0 + "ImageSize").FirstOrDefault();

        if (this.document.Descendants(BMP + "ImageSize").FirstOrDefault() != null)
            imageSize = (string)this.document.Descendants(BMP + "ImageSize").FirstOrDefault();

        if (this.document.Descendants(Composite + "ImageSize").FirstOrDefault() != null)
            imageSize = (string)this.document.Descendants(Composite + "ImageSize").FirstOrDefault();

        this.metadata.ImageSize = imageSize;
    }

    public Metadata GetMetadata()
    {
        return metadata;
    }
}

我認為首先要注意的是這兩個類的構造函數。 我傳入一個XDocument(這是xml格式的元數據,但我也傳入了fileSize,fileType和MimeType,我用它來創建我的Metadata實例。

DocumentMetadataBuilder類中,即使我實現IMetadataBuilder,大多數方法實際上都會拋出NotImplementedException 這是有目的的,並在我的Director類中變得明顯:

public class MetadataCreator
{
    private XNamespace FileNamespace = "http://ns.exiftool.ca/File/1.0/";
    private XNamespace SystemNamespace = "http://ns.exiftool.ca/File/System/1.0/";

    private IMetadataBuilder builder;

    public Metadata Create(string destination, string fileName, string exifToolPath)
    {
        var fullPath = Path.Combine(destination, fileName);
        var document = new XDocument(GetFullXml(fullPath, exifToolPath));

        var fileSize = (string)document.Descendants(SystemNamespace + "FileSize").FirstOrDefault();
        var fileType = (string)document.Descendants(FileNamespace + "FileType").FirstOrDefault();
        var mimeType = (string)document.Descendants(FileNamespace + "MIMEType").FirstOrDefault();
        var type = mimeType.ToLower().Split('/')[0];

        switch (type)
        {
            case "image":

                builder = new ImageMetadataBuilder(document, fileSize, fileType, mimeType);
                builder.SetCreateDate();
                builder.SetModifyDate();
                builder.SetImageWidth();
                builder.SetImageHeight();
                builder.SetImageSize();

                break;

            case "document":

                builder = new DocumentMetadataBuilder(document, fileSize, fileType, mimeType);
                builder.SetCreateDate();
                builder.SetModifyDate();

                break;
        }

        return builder.GetMetadata();
    }

    private XElement GetFullXml(string filePath, string exifToolPath)
    {
        string args = string.Format("-X \"{0}\"", filePath);
        string output = RunProcess(exifToolPath, args);
        output = Sanitize(output);

        return new XElement("FullMetadata", XElement.Parse(output));
    }
    private string GenerateId()
    {
        long i = 1;
        foreach (byte b in Guid.NewGuid().ToByteArray())
        {
            i *= ((int)b + 1);
        }
        return string.Format("{0:x}", i - DateTime.Now.Ticks);
    }

    private string RunProcess(string exifToolPath, string args)
    {
        if (String.IsNullOrEmpty(exifToolPath))
            throw new SystemException("EXIFTool Executable Path Not Configured");

        if (!System.IO.File.Exists(exifToolPath))
            throw new SystemException("EXIFTool Executable Not Found: " + exifToolPath);

        var process = new Process
        {
            StartInfo =
            {
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                FileName = exifToolPath,
                Arguments = args
            }
        };

        process.Start();

        var output = process.StandardOutput.ReadToEnd();
        process.WaitForExit();

        return output;
    }

    private string Sanitize(string s)
    {
        return s.Replace("&", string.Empty);
    }
}

現在,使用Director類,我決定將GetCreate結合到一個方法中,因為我不想調用Create方法然后每次都調用Get方法。 在調用Create時,我沒有理由不返回Metadata類。

另一件需要注意的是,我基於IMetadataBuilder具體類調用了不同的方法。 ImageMetadataBuilder調用所有公共方法, DocumentMetadataBuilder只調用SetCreateDateSetModifyDate

然后你可以看到我返回IMetadataBuilder * GetMetadata *方法。

這被稱為:

var metadata = (asset.Metadata == null) ? new MetadataCreator().Create(this.uploadPath, asset.FileName, this.exifToolPath) : asset.Metadata;

除非我完全錯過了這艘船,否則我相信這是Builder Design Pattern的一個很好的例子

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM